pdd-cli 0.0.45__py3-none-any.whl → 0.0.118__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.
Files changed (195) hide show
  1. pdd/__init__.py +40 -8
  2. pdd/agentic_bug.py +323 -0
  3. pdd/agentic_bug_orchestrator.py +497 -0
  4. pdd/agentic_change.py +231 -0
  5. pdd/agentic_change_orchestrator.py +526 -0
  6. pdd/agentic_common.py +598 -0
  7. pdd/agentic_crash.py +534 -0
  8. pdd/agentic_e2e_fix.py +319 -0
  9. pdd/agentic_e2e_fix_orchestrator.py +426 -0
  10. pdd/agentic_fix.py +1294 -0
  11. pdd/agentic_langtest.py +162 -0
  12. pdd/agentic_update.py +387 -0
  13. pdd/agentic_verify.py +183 -0
  14. pdd/architecture_sync.py +565 -0
  15. pdd/auth_service.py +210 -0
  16. pdd/auto_deps_main.py +71 -51
  17. pdd/auto_include.py +245 -5
  18. pdd/auto_update.py +125 -47
  19. pdd/bug_main.py +196 -23
  20. pdd/bug_to_unit_test.py +2 -0
  21. pdd/change_main.py +11 -4
  22. pdd/cli.py +22 -1181
  23. pdd/cmd_test_main.py +350 -150
  24. pdd/code_generator.py +60 -18
  25. pdd/code_generator_main.py +790 -57
  26. pdd/commands/__init__.py +48 -0
  27. pdd/commands/analysis.py +306 -0
  28. pdd/commands/auth.py +309 -0
  29. pdd/commands/connect.py +290 -0
  30. pdd/commands/fix.py +163 -0
  31. pdd/commands/generate.py +257 -0
  32. pdd/commands/maintenance.py +175 -0
  33. pdd/commands/misc.py +87 -0
  34. pdd/commands/modify.py +256 -0
  35. pdd/commands/report.py +144 -0
  36. pdd/commands/sessions.py +284 -0
  37. pdd/commands/templates.py +215 -0
  38. pdd/commands/utility.py +110 -0
  39. pdd/config_resolution.py +58 -0
  40. pdd/conflicts_main.py +8 -3
  41. pdd/construct_paths.py +589 -111
  42. pdd/context_generator.py +10 -2
  43. pdd/context_generator_main.py +175 -76
  44. pdd/continue_generation.py +53 -10
  45. pdd/core/__init__.py +33 -0
  46. pdd/core/cli.py +527 -0
  47. pdd/core/cloud.py +237 -0
  48. pdd/core/dump.py +554 -0
  49. pdd/core/errors.py +67 -0
  50. pdd/core/remote_session.py +61 -0
  51. pdd/core/utils.py +90 -0
  52. pdd/crash_main.py +262 -33
  53. pdd/data/language_format.csv +71 -63
  54. pdd/data/llm_model.csv +20 -18
  55. pdd/detect_change_main.py +5 -4
  56. pdd/docs/prompting_guide.md +864 -0
  57. pdd/docs/whitepaper_with_benchmarks/data_and_functions/benchmark_analysis.py +495 -0
  58. pdd/docs/whitepaper_with_benchmarks/data_and_functions/creation_compare.py +528 -0
  59. pdd/fix_code_loop.py +523 -95
  60. pdd/fix_code_module_errors.py +6 -2
  61. pdd/fix_error_loop.py +491 -92
  62. pdd/fix_errors_from_unit_tests.py +4 -3
  63. pdd/fix_main.py +278 -21
  64. pdd/fix_verification_errors.py +12 -100
  65. pdd/fix_verification_errors_loop.py +529 -286
  66. pdd/fix_verification_main.py +294 -89
  67. pdd/frontend/dist/assets/index-B5DZHykP.css +1 -0
  68. pdd/frontend/dist/assets/index-DQ3wkeQ2.js +449 -0
  69. pdd/frontend/dist/index.html +376 -0
  70. pdd/frontend/dist/logo.svg +33 -0
  71. pdd/generate_output_paths.py +139 -15
  72. pdd/generate_test.py +218 -146
  73. pdd/get_comment.py +19 -44
  74. pdd/get_extension.py +8 -9
  75. pdd/get_jwt_token.py +318 -22
  76. pdd/get_language.py +8 -7
  77. pdd/get_run_command.py +75 -0
  78. pdd/get_test_command.py +68 -0
  79. pdd/git_update.py +70 -19
  80. pdd/incremental_code_generator.py +2 -2
  81. pdd/insert_includes.py +13 -4
  82. pdd/llm_invoke.py +1711 -181
  83. pdd/load_prompt_template.py +19 -12
  84. pdd/path_resolution.py +140 -0
  85. pdd/pdd_completion.fish +25 -2
  86. pdd/pdd_completion.sh +30 -4
  87. pdd/pdd_completion.zsh +79 -4
  88. pdd/postprocess.py +14 -4
  89. pdd/preprocess.py +293 -24
  90. pdd/preprocess_main.py +41 -6
  91. pdd/prompts/agentic_bug_step10_pr_LLM.prompt +182 -0
  92. pdd/prompts/agentic_bug_step1_duplicate_LLM.prompt +73 -0
  93. pdd/prompts/agentic_bug_step2_docs_LLM.prompt +129 -0
  94. pdd/prompts/agentic_bug_step3_triage_LLM.prompt +95 -0
  95. pdd/prompts/agentic_bug_step4_reproduce_LLM.prompt +97 -0
  96. pdd/prompts/agentic_bug_step5_root_cause_LLM.prompt +123 -0
  97. pdd/prompts/agentic_bug_step6_test_plan_LLM.prompt +107 -0
  98. pdd/prompts/agentic_bug_step7_generate_LLM.prompt +172 -0
  99. pdd/prompts/agentic_bug_step8_verify_LLM.prompt +119 -0
  100. pdd/prompts/agentic_bug_step9_e2e_test_LLM.prompt +289 -0
  101. pdd/prompts/agentic_change_step10_identify_issues_LLM.prompt +1006 -0
  102. pdd/prompts/agentic_change_step11_fix_issues_LLM.prompt +984 -0
  103. pdd/prompts/agentic_change_step12_create_pr_LLM.prompt +131 -0
  104. pdd/prompts/agentic_change_step1_duplicate_LLM.prompt +73 -0
  105. pdd/prompts/agentic_change_step2_docs_LLM.prompt +101 -0
  106. pdd/prompts/agentic_change_step3_research_LLM.prompt +126 -0
  107. pdd/prompts/agentic_change_step4_clarify_LLM.prompt +164 -0
  108. pdd/prompts/agentic_change_step5_docs_change_LLM.prompt +981 -0
  109. pdd/prompts/agentic_change_step6_devunits_LLM.prompt +1005 -0
  110. pdd/prompts/agentic_change_step7_architecture_LLM.prompt +1044 -0
  111. pdd/prompts/agentic_change_step8_analyze_LLM.prompt +1027 -0
  112. pdd/prompts/agentic_change_step9_implement_LLM.prompt +1077 -0
  113. pdd/prompts/agentic_crash_explore_LLM.prompt +49 -0
  114. pdd/prompts/agentic_e2e_fix_step1_unit_tests_LLM.prompt +90 -0
  115. pdd/prompts/agentic_e2e_fix_step2_e2e_tests_LLM.prompt +91 -0
  116. pdd/prompts/agentic_e2e_fix_step3_root_cause_LLM.prompt +89 -0
  117. pdd/prompts/agentic_e2e_fix_step4_fix_e2e_tests_LLM.prompt +96 -0
  118. pdd/prompts/agentic_e2e_fix_step5_identify_devunits_LLM.prompt +91 -0
  119. pdd/prompts/agentic_e2e_fix_step6_create_unit_tests_LLM.prompt +106 -0
  120. pdd/prompts/agentic_e2e_fix_step7_verify_tests_LLM.prompt +116 -0
  121. pdd/prompts/agentic_e2e_fix_step8_run_pdd_fix_LLM.prompt +120 -0
  122. pdd/prompts/agentic_e2e_fix_step9_verify_all_LLM.prompt +146 -0
  123. pdd/prompts/agentic_fix_explore_LLM.prompt +45 -0
  124. pdd/prompts/agentic_fix_harvest_only_LLM.prompt +48 -0
  125. pdd/prompts/agentic_fix_primary_LLM.prompt +85 -0
  126. pdd/prompts/agentic_update_LLM.prompt +925 -0
  127. pdd/prompts/agentic_verify_explore_LLM.prompt +45 -0
  128. pdd/prompts/auto_include_LLM.prompt +122 -905
  129. pdd/prompts/change_LLM.prompt +3093 -1
  130. pdd/prompts/detect_change_LLM.prompt +686 -27
  131. pdd/prompts/example_generator_LLM.prompt +22 -1
  132. pdd/prompts/extract_code_LLM.prompt +5 -1
  133. pdd/prompts/extract_program_code_fix_LLM.prompt +7 -1
  134. pdd/prompts/extract_prompt_update_LLM.prompt +7 -8
  135. pdd/prompts/extract_promptline_LLM.prompt +17 -11
  136. pdd/prompts/find_verification_errors_LLM.prompt +6 -0
  137. pdd/prompts/fix_code_module_errors_LLM.prompt +12 -2
  138. pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +9 -0
  139. pdd/prompts/fix_verification_errors_LLM.prompt +22 -0
  140. pdd/prompts/generate_test_LLM.prompt +41 -7
  141. pdd/prompts/generate_test_from_example_LLM.prompt +115 -0
  142. pdd/prompts/increase_tests_LLM.prompt +1 -5
  143. pdd/prompts/insert_includes_LLM.prompt +316 -186
  144. pdd/prompts/prompt_code_diff_LLM.prompt +119 -0
  145. pdd/prompts/prompt_diff_LLM.prompt +82 -0
  146. pdd/prompts/trace_LLM.prompt +25 -22
  147. pdd/prompts/unfinished_prompt_LLM.prompt +85 -1
  148. pdd/prompts/update_prompt_LLM.prompt +22 -1
  149. pdd/pytest_output.py +127 -12
  150. pdd/remote_session.py +876 -0
  151. pdd/render_mermaid.py +236 -0
  152. pdd/server/__init__.py +52 -0
  153. pdd/server/app.py +335 -0
  154. pdd/server/click_executor.py +587 -0
  155. pdd/server/executor.py +338 -0
  156. pdd/server/jobs.py +661 -0
  157. pdd/server/models.py +241 -0
  158. pdd/server/routes/__init__.py +31 -0
  159. pdd/server/routes/architecture.py +451 -0
  160. pdd/server/routes/auth.py +364 -0
  161. pdd/server/routes/commands.py +929 -0
  162. pdd/server/routes/config.py +42 -0
  163. pdd/server/routes/files.py +603 -0
  164. pdd/server/routes/prompts.py +1322 -0
  165. pdd/server/routes/websocket.py +473 -0
  166. pdd/server/security.py +243 -0
  167. pdd/server/terminal_spawner.py +209 -0
  168. pdd/server/token_counter.py +222 -0
  169. pdd/setup_tool.py +648 -0
  170. pdd/simple_math.py +2 -0
  171. pdd/split_main.py +3 -2
  172. pdd/summarize_directory.py +237 -195
  173. pdd/sync_animation.py +8 -4
  174. pdd/sync_determine_operation.py +839 -112
  175. pdd/sync_main.py +351 -57
  176. pdd/sync_orchestration.py +1400 -756
  177. pdd/sync_tui.py +848 -0
  178. pdd/template_expander.py +161 -0
  179. pdd/template_registry.py +264 -0
  180. pdd/templates/architecture/architecture_json.prompt +237 -0
  181. pdd/templates/generic/generate_prompt.prompt +174 -0
  182. pdd/trace.py +168 -12
  183. pdd/trace_main.py +4 -3
  184. pdd/track_cost.py +140 -63
  185. pdd/unfinished_prompt.py +51 -4
  186. pdd/update_main.py +567 -67
  187. pdd/update_model_costs.py +2 -2
  188. pdd/update_prompt.py +19 -4
  189. {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/METADATA +29 -11
  190. pdd_cli-0.0.118.dist-info/RECORD +227 -0
  191. {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/licenses/LICENSE +1 -1
  192. pdd_cli-0.0.45.dist-info/RECORD +0 -116
  193. {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/WHEEL +0 -0
  194. {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/entry_points.txt +0 -0
  195. {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/top_level.txt +0 -0
pdd/cli.py CHANGED
@@ -7,1196 +7,37 @@ generating code, tests, fixing issues, and managing prompts.
7
7
  """
8
8
  from __future__ import annotations
9
9
 
10
- import os
11
- from typing import Any, Dict, List, Optional, Tuple
12
- from pathlib import Path # Import Path
10
+ from .core.cli import cli
11
+ from .commands import register_commands
13
12
 
14
- import click
15
- from rich.console import Console
16
- from rich.theme import Theme
17
- from rich.markup import MarkupError, escape
13
+ # Register all commands
14
+ register_commands(cli)
18
15
 
19
- # --- Relative Imports for Internal Modules ---
20
- from . import DEFAULT_STRENGTH, __version__, DEFAULT_TIME
21
- from .auto_deps_main import auto_deps_main
16
+ # Re-export commonly used items for backward compatibility
17
+ from .commands.templates import templates_group
22
18
  from .auto_update import auto_update
23
- from .bug_main import bug_main
24
- from .change_main import change_main
25
- from .cmd_test_main import cmd_test_main
26
19
  from .code_generator_main import code_generator_main
27
- from .conflicts_main import conflicts_main
28
- # Need to import construct_paths for tests patching pdd.cli.construct_paths
29
- from .construct_paths import construct_paths
30
20
  from .context_generator_main import context_generator_main
31
- from .crash_main import crash_main
32
- from .detect_change_main import detect_change_main
21
+ from .cmd_test_main import cmd_test_main
33
22
  from .fix_main import fix_main
34
- from .fix_verification_main import fix_verification_main
35
- from .install_completion import install_completion, get_local_pdd_path
36
- from .preprocess_main import preprocess_main
37
23
  from .split_main import split_main
24
+ from .change_main import change_main
25
+ from .update_main import update_main
38
26
  from .sync_main import sync_main
27
+ from .auto_deps_main import auto_deps_main
28
+ from .detect_change_main import detect_change_main
29
+ from .conflicts_main import conflicts_main
30
+ from .bug_main import bug_main
31
+ from .crash_main import crash_main
39
32
  from .trace_main import trace_main
40
- from .track_cost import track_cost
41
- from .update_main import update_main
42
-
43
-
44
- # --- Initialize Rich Console ---
45
- # Define a custom theme for consistent styling
46
- custom_theme = Theme({
47
- "info": "cyan",
48
- "warning": "yellow",
49
- "error": "bold red",
50
- "success": "green",
51
- "path": "dim blue",
52
- "command": "bold magenta",
53
- })
54
- console = Console(theme=custom_theme)
55
-
56
- # --- Helper Function for Error Handling ---
57
- def handle_error(exception: Exception, command_name: str, quiet: bool):
58
- """Prints error messages using Rich console.""" # Modified docstring
59
- if not quiet:
60
- console.print(f"[error]Error during '{command_name}' command:[/error]", style="error")
61
- if isinstance(exception, FileNotFoundError):
62
- console.print(f" [error]File not found:[/error] {exception}", style="error")
63
- elif isinstance(exception, (ValueError, IOError)):
64
- console.print(f" [error]Input/Output Error:[/error] {exception}", style="error")
65
- elif isinstance(exception, click.UsageError): # Handle Click usage errors explicitly if needed
66
- console.print(f" [error]Usage Error:[/error] {exception}", style="error")
67
- # click.UsageError should typically exit with 2, but we are handling it.
68
- elif isinstance(exception, MarkupError):
69
- console.print(" [error]Markup Error:[/error] Invalid Rich markup encountered.", style="error")
70
- # Print the error message safely escaped
71
- console.print(escape(str(exception)))
72
- else:
73
- console.print(f" [error]An unexpected error occurred:[/error] {exception}", style="error")
74
- # Do NOT re-raise e here. Let the command function return None.
75
-
76
-
77
- # --- Main CLI Group ---
78
- @click.group(chain=True, help="PDD (Prompt-Driven Development) Command Line Interface.")
79
- @click.option(
80
- "--force",
81
- is_flag=True,
82
- default=False,
83
- help="Overwrite existing files without asking for confirmation.",
84
- )
85
- @click.option(
86
- "--strength",
87
- type=click.FloatRange(0.0, 1.0),
88
- default=DEFAULT_STRENGTH,
89
- show_default=True,
90
- help="Set the strength of the AI model (0.0 to 1.0).",
91
- )
92
- @click.option(
93
- "--temperature",
94
- type=click.FloatRange(0.0, 2.0), # Allow higher temperatures if needed
95
- default=0.0,
96
- show_default=True,
97
- help="Set the temperature of the AI model.",
98
- )
99
- @click.option(
100
- "--time",
101
- type=click.FloatRange(0.0, 1.0),
102
- default=None,
103
- show_default=True,
104
- help="Controls reasoning allocation for LLMs (0.0-1.0). Uses DEFAULT_TIME if None.",
105
- )
106
- @click.option(
107
- "--verbose",
108
- is_flag=True,
109
- default=False,
110
- help="Increase output verbosity for more detailed information.",
111
- )
112
- @click.option(
113
- "--quiet",
114
- is_flag=True,
115
- default=False,
116
- help="Decrease output verbosity for minimal information.",
117
- )
118
- @click.option(
119
- "--output-cost",
120
- type=click.Path(dir_okay=False, writable=True),
121
- default=None,
122
- help="Enable cost tracking and output a CSV file with usage details.",
123
- )
124
- @click.option(
125
- "--review-examples",
126
- is_flag=True,
127
- default=False,
128
- help="Review and optionally exclude few-shot examples before command execution.",
129
- )
130
- @click.option(
131
- "--local",
132
- is_flag=True,
133
- default=False,
134
- help="Run commands locally instead of in the cloud.",
135
- )
136
- @click.version_option(version=__version__, package_name="pdd-cli")
137
- @click.pass_context
138
- def cli(
139
- ctx: click.Context,
140
- force: bool,
141
- strength: float,
142
- temperature: float,
143
- verbose: bool,
144
- quiet: bool,
145
- output_cost: Optional[str],
146
- review_examples: bool,
147
- local: bool,
148
- time: Optional[float], # Type hint is Optional[float]
149
- ):
150
- """
151
- Main entry point for the PDD CLI. Handles global options and initializes context.
152
- Supports multi-command chaining.
153
- """
154
- # Ensure PDD_PATH is set before any commands run
155
- get_local_pdd_path()
156
-
157
- ctx.ensure_object(dict)
158
- ctx.obj["force"] = force
159
- ctx.obj["strength"] = strength
160
- ctx.obj["temperature"] = temperature
161
- ctx.obj["verbose"] = verbose
162
- ctx.obj["quiet"] = quiet
163
- ctx.obj["output_cost"] = output_cost
164
- ctx.obj["review_examples"] = review_examples
165
- ctx.obj["local"] = local
166
- # Use DEFAULT_TIME if time is not provided
167
- ctx.obj["time"] = time if time is not None else DEFAULT_TIME
168
-
169
- # Suppress verbose if quiet is enabled
170
- if quiet:
171
- ctx.obj["verbose"] = False
172
-
173
- # Perform auto-update check unless disabled
174
- if os.getenv("PDD_AUTO_UPDATE", "true").lower() != "false":
175
- try:
176
- if not quiet:
177
- console.print("[info]Checking for updates...[/info]")
178
- # Removed quiet=quiet argument as it caused TypeError
179
- auto_update()
180
- except Exception as exception: # Using more descriptive name
181
- if not quiet:
182
- console.print(
183
- f"[warning]Auto-update check failed:[/warning] {exception}",
184
- style="warning"
185
- )
186
-
187
- # --- Result Callback for Chained Commands ---
188
- @cli.result_callback()
189
- @click.pass_context
190
- def process_commands(ctx: click.Context, results: List[Optional[Tuple[Any, float, str]]], **kwargs):
191
- """
192
- Processes the results from chained commands.
193
-
194
- Receives a list of tuples, typically (result, cost, model_name),
195
- or None from each command function.
196
- """
197
- total_chain_cost = 0.0
198
- # Get invoked subcommands directly from the group context if available (safer for testing)
199
- # Note: This might yield "Unknown Command" during tests with CliRunner
200
- invoked_subcommands = getattr(ctx, 'invoked_subcommands', [])
201
- num_commands = len(invoked_subcommands)
202
- num_results = len(results) # Number of results actually received
203
-
204
- if not ctx.obj.get("quiet"):
205
- console.print("\n[info]--- Command Chain Execution Summary ---[/info]")
206
-
207
- for i, result_tuple in enumerate(results):
208
- # Use the retrieved subcommand name (might be "Unknown Command X" in tests)
209
- command_name = invoked_subcommands[i] if i < num_commands else f"Unknown Command {i+1}"
210
-
211
- # Check if the command failed (returned None)
212
- if result_tuple is None:
213
- if not ctx.obj.get("quiet"):
214
- # Check if it was install_completion (which normally returns None)
215
- if command_name == "install_completion":
216
- console.print(f" [info]Step {i+1} ({command_name}):[/info] Command completed.")
217
- # If command name is unknown, and it might be install_completion which prints its own status
218
- elif command_name.startswith("Unknown Command"):
219
- console.print(f" [info]Step {i+1} ({command_name}):[/info] Command executed (see output above for status details).")
220
- # Check if it was preprocess (which returns a dummy tuple on success)
221
- # This case handles actual failure for preprocess
222
- elif command_name == "preprocess":
223
- console.print(f" [error]Step {i+1} ({command_name}):[/error] Command failed.")
224
- else:
225
- console.print(f" [error]Step {i+1} ({command_name}):[/error] Command failed.")
226
- # Check if the result is the expected tuple structure from @track_cost or preprocess success
227
- elif isinstance(result_tuple, tuple) and len(result_tuple) == 3:
228
- _result_data, cost, model_name = result_tuple
229
- total_chain_cost += cost
230
- if not ctx.obj.get("quiet"):
231
- # Special handling for preprocess success message (check actual command name)
232
- actual_command_name = invoked_subcommands[i] if i < num_commands else None # Get actual name if possible
233
- if actual_command_name == "preprocess" and cost == 0.0 and model_name == "local":
234
- console.print(f" [info]Step {i+1} ({command_name}):[/info] Command completed (local).")
235
- else:
236
- # Generic output using potentially "Unknown Command" name
237
- console.print(f" [info]Step {i+1} ({command_name}):[/info] Cost: ${cost:.6f}, Model: {model_name}")
238
- else:
239
- # Handle unexpected return types if necessary
240
- if not ctx.obj.get("quiet"):
241
- # Provide more detail on the unexpected type
242
- console.print(f" [warning]Step {i+1} ({command_name}):[/warning] Unexpected result format: {type(result_tuple).__name__} - {str(result_tuple)[:50]}...")
243
-
244
-
245
- if not ctx.obj.get("quiet"):
246
- # Only print total cost if at least one command potentially contributed cost
247
- if any(res is not None and isinstance(res, tuple) and len(res) == 3 for res in results):
248
- console.print(f"[info]Total Estimated Cost for Chain:[/info] ${total_chain_cost:.6f}")
249
- # Indicate if the chain might have been incomplete due to errors
250
- if num_results < num_commands and not all(res is None for res in results): # Avoid printing if all failed
251
- console.print("[warning]Note: Chain may have terminated early due to errors.[/warning]")
252
- console.print("[info]-------------------------------------[/info]")
253
-
254
-
255
- # --- Command Definitions ---
256
-
257
- @cli.command("generate")
258
- @click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False))
259
- @click.option(
260
- "--output",
261
- type=click.Path(writable=True),
262
- default=None,
263
- help="Specify where to save the generated code (file or directory).",
264
- )
265
- @click.option(
266
- "--original-prompt",
267
- "original_prompt_file_path",
268
- type=click.Path(exists=True, dir_okay=False),
269
- default=None,
270
- help="Path to the original prompt file for incremental generation.",
271
- )
272
- @click.option(
273
- "--force-incremental",
274
- "force_incremental_flag",
275
- is_flag=True,
276
- default=False,
277
- help="Force incremental generation even if full regeneration is suggested.",
278
- )
279
- @click.pass_context
280
- @track_cost
281
- def generate(
282
- ctx: click.Context,
283
- prompt_file: str,
284
- output: Optional[str],
285
- original_prompt_file_path: Optional[str],
286
- force_incremental_flag: bool,
287
- ) -> Optional[Tuple[str, float, str]]:
288
- """Generate code from a prompt file."""
289
- try:
290
- generated_code, incremental, total_cost, model_name = code_generator_main(
291
- ctx=ctx,
292
- prompt_file=prompt_file,
293
- output=output,
294
- original_prompt_file_path=original_prompt_file_path,
295
- force_incremental_flag=force_incremental_flag,
296
- )
297
- return generated_code, total_cost, model_name
298
- except Exception as exception:
299
- handle_error(exception, "generate", ctx.obj.get("quiet", False))
300
- return None
301
-
302
-
303
- @cli.command("example")
304
- @click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False))
305
- @click.argument("code_file", type=click.Path(exists=True, dir_okay=False))
306
- @click.option(
307
- "--output",
308
- type=click.Path(writable=True),
309
- default=None,
310
- help="Specify where to save the generated example code (file or directory).",
311
- )
312
- @click.pass_context
313
- @track_cost
314
- def example(
315
- ctx: click.Context,
316
- prompt_file: str,
317
- code_file: str,
318
- output: Optional[str]
319
- ) -> Optional[Tuple[str, float, str]]:
320
- """Generate example code for a given prompt and implementation."""
321
- try:
322
- example_code, total_cost, model_name = context_generator_main(
323
- ctx=ctx,
324
- prompt_file=prompt_file,
325
- code_file=code_file,
326
- output=output,
327
- )
328
- return example_code, total_cost, model_name
329
- except Exception as exception:
330
- handle_error(exception, "example", ctx.obj.get("quiet", False))
331
- return None
332
-
333
-
334
- @cli.command("test")
335
- @click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False))
336
- @click.argument("code_file", type=click.Path(exists=True, dir_okay=False))
337
- @click.option(
338
- "--output",
339
- type=click.Path(writable=True),
340
- default=None,
341
- help="Specify where to save the generated test file (file or directory).",
342
- )
343
- @click.option(
344
- "--language",
345
- type=str,
346
- default=None,
347
- help="Specify the programming language."
348
- )
349
- @click.option(
350
- "--coverage-report",
351
- type=click.Path(exists=True, dir_okay=False),
352
- default=None,
353
- help="Path to the coverage report file for existing tests.",
354
- )
355
- @click.option(
356
- "--existing-tests",
357
- type=click.Path(exists=True, dir_okay=False),
358
- default=None,
359
- help="Path to the existing unit test file.",
360
- )
361
- @click.option(
362
- "--target-coverage",
363
- type=click.FloatRange(0.0, 100.0),
364
- default=None, # Use None, default handled in cmd_test_main or env var
365
- help="Desired code coverage percentage (default: 10.0 or PDD_TEST_COVERAGE_TARGET).",
366
- )
367
- @click.option(
368
- "--merge",
369
- is_flag=True,
370
- default=False,
371
- help="Merge new tests with existing test file instead of creating a separate file.",
372
- )
373
- @click.pass_context
374
- @track_cost
375
- def test(
376
- ctx: click.Context,
377
- prompt_file: str,
378
- code_file: str,
379
- output: Optional[str],
380
- language: Optional[str],
381
- coverage_report: Optional[str],
382
- existing_tests: Optional[str],
383
- target_coverage: Optional[float],
384
- merge: bool,
385
- ) -> Optional[Tuple[str, float, str]]:
386
- """Generate unit tests for a given prompt and implementation."""
387
- try:
388
- test_code, total_cost, model_name = cmd_test_main(
389
- ctx=ctx,
390
- prompt_file=prompt_file,
391
- code_file=code_file,
392
- output=output,
393
- language=language,
394
- coverage_report=coverage_report,
395
- existing_tests=existing_tests,
396
- target_coverage=target_coverage,
397
- merge=merge,
398
- )
399
- return test_code, total_cost, model_name
400
- except Exception as exception:
401
- handle_error(exception, "test", ctx.obj.get("quiet", False))
402
- return None
403
-
404
-
405
- @cli.command("preprocess")
406
- @click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False))
407
- @click.option(
408
- "--output",
409
- type=click.Path(writable=True),
410
- default=None,
411
- help="Specify where to save the preprocessed prompt file (file or directory).",
412
- )
413
- @click.option(
414
- "--xml",
415
- is_flag=True,
416
- default=False,
417
- help="Insert XML delimiters for structure (minimal preprocessing).",
418
- )
419
- @click.option(
420
- "--recursive",
421
- is_flag=True,
422
- default=False,
423
- help="Recursively preprocess includes.",
424
- )
425
- @click.option(
426
- "--double",
427
- is_flag=True,
428
- default=False,
429
- help="Double curly brackets.",
430
- )
431
- @click.option(
432
- "--exclude",
433
- multiple=True,
434
- default=None,
435
- help="List of keys to exclude from curly bracket doubling.",
436
- )
437
- @click.pass_context
438
- # No @track_cost as preprocessing is local, but return dummy tuple for callback
439
- def preprocess(
440
- ctx: click.Context,
441
- prompt_file: str,
442
- output: Optional[str],
443
- xml: bool,
444
- recursive: bool,
445
- double: bool,
446
- exclude: Optional[Tuple[str, ...]],
447
- ) -> Optional[Tuple[str, float, str]]:
448
- """Preprocess a prompt file to prepare it for LLM use."""
449
- try:
450
- # Since preprocess is a local operation, we don't track cost
451
- # But we need to return a tuple in the expected format for result callback
452
- result = preprocess_main(
453
- ctx=ctx,
454
- prompt_file=prompt_file,
455
- output=output,
456
- xml=xml,
457
- recursive=recursive,
458
- double=double,
459
- exclude=list(exclude) if exclude else [],
460
- )
461
-
462
- # Handle the result from preprocess_main
463
- if result is None:
464
- # If preprocess_main returns None, still return a dummy tuple for the callback
465
- return "", 0.0, "local"
466
- else:
467
- # Unpack the return value from preprocess_main
468
- processed_prompt, total_cost, model_name = result
469
- return processed_prompt, total_cost, model_name
470
- except Exception as exception:
471
- handle_error(exception, "preprocess", ctx.obj.get("quiet", False))
472
- return None
473
-
474
-
475
- @cli.command("fix")
476
- @click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False))
477
- @click.argument("code_file", type=click.Path(exists=True, dir_okay=False))
478
- @click.argument("unit_test_file", type=click.Path(exists=True, dir_okay=False))
479
- @click.argument("error_file", type=click.Path(dir_okay=False)) # Allow non-existent for loop mode
480
- @click.option(
481
- "--output-test",
482
- type=click.Path(writable=True),
483
- default=None,
484
- help="Specify where to save the fixed unit test file (file or directory).",
485
- )
486
- @click.option(
487
- "--output-code",
488
- type=click.Path(writable=True),
489
- default=None,
490
- help="Specify where to save the fixed code file (file or directory).",
491
- )
492
- @click.option(
493
- "--output-results",
494
- type=click.Path(writable=True),
495
- default=None,
496
- help="Specify where to save the results log (file or directory).",
497
- )
498
- @click.option(
499
- "--loop",
500
- is_flag=True,
501
- default=False,
502
- help="Enable iterative fixing process."
503
- )
504
- @click.option(
505
- "--verification-program",
506
- type=click.Path(exists=True, dir_okay=False),
507
- default=None,
508
- help="Path to a Python program that verifies the fix.",
509
- )
510
- @click.option(
511
- "--max-attempts",
512
- type=int,
513
- default=3,
514
- show_default=True,
515
- help="Maximum number of fix attempts.",
516
- )
517
- @click.option(
518
- "--budget",
519
- type=float,
520
- default=5.0,
521
- show_default=True,
522
- help="Maximum cost allowed for the fixing process.",
523
- )
524
- @click.option(
525
- "--auto-submit",
526
- is_flag=True,
527
- default=False,
528
- help="Automatically submit the example if all unit tests pass.",
529
- )
530
- @click.pass_context
531
- @track_cost
532
- def fix(
533
- ctx: click.Context,
534
- prompt_file: str,
535
- code_file: str,
536
- unit_test_file: str,
537
- error_file: str,
538
- output_test: Optional[str],
539
- output_code: Optional[str],
540
- output_results: Optional[str],
541
- loop: bool,
542
- verification_program: Optional[str],
543
- max_attempts: int,
544
- budget: float,
545
- auto_submit: bool,
546
- ) -> Optional[Tuple[Dict[str, Any], float, str]]:
547
- """Fix code based on a prompt and unit test errors."""
548
- try:
549
- # The actual logic is in fix_main
550
- success, fixed_unit_test, fixed_code, attempts, total_cost, model_name = fix_main(
551
- ctx=ctx,
552
- prompt_file=prompt_file,
553
- code_file=code_file,
554
- unit_test_file=unit_test_file,
555
- error_file=error_file,
556
- output_test=output_test,
557
- output_code=output_code,
558
- output_results=output_results,
559
- loop=loop,
560
- verification_program=verification_program,
561
- max_attempts=max_attempts,
562
- budget=budget,
563
- auto_submit=auto_submit,
564
- )
565
- result = {
566
- "success": success,
567
- "fixed_unit_test": fixed_unit_test,
568
- "fixed_code": fixed_code,
569
- "attempts": attempts,
570
- }
571
- return result, total_cost, model_name
572
- except Exception as exception:
573
- handle_error(exception, "fix", ctx.obj.get("quiet", False))
574
- return None
575
-
576
-
577
- @cli.command("split")
578
- @click.argument("input_prompt", type=click.Path(exists=True, dir_okay=False))
579
- @click.argument("input_code", type=click.Path(exists=True, dir_okay=False))
580
- @click.argument("example_code", type=click.Path(exists=True, dir_okay=False))
581
- @click.option(
582
- "--output-sub",
583
- type=click.Path(writable=True),
584
- default=None,
585
- help="Specify where to save the generated sub-prompt file (file or directory).",
586
- )
587
- @click.option(
588
- "--output-modified",
589
- type=click.Path(writable=True),
590
- default=None,
591
- help="Specify where to save the modified prompt file (file or directory).",
592
- )
593
- @click.pass_context
594
- @track_cost
595
- def split(
596
- ctx: click.Context,
597
- input_prompt: str,
598
- input_code: str,
599
- example_code: str,
600
- output_sub: Optional[str],
601
- output_modified: Optional[str],
602
- ) -> Optional[Tuple[Dict[str, str], float, str]]: # Modified return type
603
- """Split large complex prompt files into smaller ones."""
604
- quiet = ctx.obj.get("quiet", False)
605
- command_name = "split"
606
- try:
607
- result_data, total_cost, model_name = split_main(
608
- ctx=ctx,
609
- input_prompt_file=input_prompt,
610
- input_code_file=input_code,
611
- example_code_file=example_code,
612
- output_sub=output_sub,
613
- output_modified=output_modified,
614
- )
615
- return result_data, total_cost, model_name
616
- except Exception as e:
617
- handle_error(e, command_name, quiet)
618
- return None # Return None on failure
619
-
620
-
621
- @cli.command("change")
622
- @click.argument("change_prompt_file", type=click.Path(exists=True, dir_okay=False))
623
- @click.argument("input_code", type=click.Path(exists=True)) # Can be file or dir
624
- @click.argument("input_prompt_file", type=click.Path(exists=True, dir_okay=False), required=False)
625
- @click.option(
626
- "--budget",
627
- type=float,
628
- default=5.0,
629
- show_default=True,
630
- help="Maximum cost allowed for the change process.",
631
- )
632
- @click.option(
633
- "--output",
634
- type=click.Path(writable=True),
635
- default=None,
636
- help="Specify where to save the modified prompt file (file or directory).",
637
- )
638
- @click.option(
639
- "--csv",
640
- "use_csv",
641
- is_flag=True,
642
- default=False,
643
- help="Use a CSV file for batch change prompts.",
644
- )
645
- @click.pass_context
646
- @track_cost
647
- def change(
648
- ctx: click.Context,
649
- change_prompt_file: str,
650
- input_code: str,
651
- input_prompt_file: Optional[str],
652
- output: Optional[str],
653
- use_csv: bool,
654
- budget: float,
655
- ) -> Optional[Tuple[str | Dict, float, str]]: # Modified return type
656
- """Modify prompt(s) based on change instructions."""
657
- quiet = ctx.obj.get("quiet", False)
658
- command_name = "change"
659
- try:
660
- # --- ADD VALIDATION LOGIC HERE ---
661
- input_code_path = Path(input_code) # Convert to Path object
662
- if use_csv:
663
- if not input_code_path.is_dir():
664
- raise click.UsageError("INPUT_CODE must be a directory when using --csv.")
665
- if input_prompt_file:
666
- raise click.UsageError("Cannot use --csv and specify an INPUT_PROMPT_FILE simultaneously.")
667
- else: # Not using CSV
668
- if not input_prompt_file:
669
- # This check might be better inside change_main, but can be here too
670
- raise click.UsageError("INPUT_PROMPT_FILE is required when not using --csv.")
671
- if not input_code_path.is_file():
672
- # This check might be better inside change_main, but can be here too
673
- raise click.UsageError("INPUT_CODE must be a file when not using --csv.")
674
- # --- END VALIDATION LOGIC ---
675
-
676
- result_data, total_cost, model_name = change_main(
677
- ctx=ctx,
678
- change_prompt_file=change_prompt_file,
679
- input_code=input_code,
680
- input_prompt_file=input_prompt_file,
681
- output=output,
682
- use_csv=use_csv,
683
- budget=budget,
684
- )
685
- return result_data, total_cost, model_name
686
- except (click.UsageError, Exception) as e: # Catch specific and general exceptions
687
- handle_error(e, command_name, quiet)
688
- return None # Return None on failure
689
-
690
-
691
- @cli.command("update")
692
- @click.argument("input_prompt_file", type=click.Path(exists=True, dir_okay=False))
693
- @click.argument("modified_code_file", type=click.Path(exists=True, dir_okay=False))
694
- @click.argument("input_code_file", type=click.Path(exists=True, dir_okay=False), required=False)
695
- @click.option(
696
- "--output",
697
- type=click.Path(writable=True),
698
- default=None,
699
- help="Specify where to save the updated prompt file. If not specified, overwrites the original prompt file to maintain it as the source of truth.",
700
- )
701
- @click.option(
702
- "--git",
703
- is_flag=True,
704
- default=False,
705
- help="Use git history to find the original code file.",
706
- )
707
- @click.pass_context
708
- @track_cost
709
- def update(
710
- ctx: click.Context,
711
- input_prompt_file: str,
712
- modified_code_file: str,
713
- input_code_file: Optional[str],
714
- output: Optional[str],
715
- git: bool,
716
- ) -> Optional[Tuple[str, float, str]]: # Modified return type
717
- """Update the original prompt file based on modified code."""
718
- quiet = ctx.obj.get("quiet", False)
719
- command_name = "update"
720
- try:
721
- if git and input_code_file:
722
- raise click.UsageError("Cannot use --git and specify an INPUT_CODE_FILE simultaneously.")
723
- if not git and not input_code_file:
724
- raise click.UsageError("INPUT_CODE_FILE is required when not using --git.")
725
-
726
- updated_prompt, total_cost, model_name = update_main(
727
- ctx=ctx,
728
- input_prompt_file=input_prompt_file,
729
- modified_code_file=modified_code_file,
730
- input_code_file=input_code_file,
731
- output=output,
732
- git=git,
733
- )
734
- return updated_prompt, total_cost, model_name
735
- except (click.UsageError, Exception) as e: # Catch specific and general exceptions
736
- handle_error(e, command_name, quiet)
737
- return None # Return None on failure
738
-
739
-
740
- @cli.command("detect")
741
- @click.argument("prompt_files", nargs=-1, type=click.Path(exists=True, dir_okay=False))
742
- @click.argument("change_file", type=click.Path(exists=True, dir_okay=False))
743
- @click.option(
744
- "--output",
745
- type=click.Path(writable=True),
746
- default=None,
747
- help="Specify where to save the CSV analysis results (file or directory).",
748
- )
749
- @click.pass_context
750
- @track_cost
751
- def detect(
752
- ctx: click.Context,
753
- prompt_files: Tuple[str, ...],
754
- change_file: str,
755
- output: Optional[str],
756
- ) -> Optional[Tuple[List[Dict[str, str]], float, str]]: # Modified return type
757
- """Analyze prompts and a change description to find needed changes."""
758
- quiet = ctx.obj.get("quiet", False)
759
- command_name = "detect"
760
- try:
761
- if not prompt_files:
762
- raise click.UsageError("At least one PROMPT_FILE must be provided.")
763
-
764
- changes_list, total_cost, model_name = detect_change_main(
765
- ctx=ctx,
766
- prompt_files=list(prompt_files),
767
- change_file=change_file,
768
- output=output,
769
- )
770
- return changes_list, total_cost, model_name
771
- except (click.UsageError, Exception) as e: # Catch specific and general exceptions
772
- handle_error(e, command_name, quiet)
773
- return None # Return None on failure
774
-
775
-
776
- @cli.command("conflicts")
777
- @click.argument("prompt1", type=click.Path(exists=True, dir_okay=False))
778
- @click.argument("prompt2", type=click.Path(exists=True, dir_okay=False))
779
- @click.option(
780
- "--output",
781
- type=click.Path(writable=True),
782
- default=None,
783
- help="Specify where to save the CSV conflict analysis results (file or directory).",
784
- )
785
- @click.pass_context
786
- @track_cost
787
- def conflicts(
788
- ctx: click.Context,
789
- prompt1: str,
790
- prompt2: str,
791
- output: Optional[str],
792
- ) -> Optional[Tuple[List[Dict[str, str]], float, str]]: # Modified return type
793
- """Analyze two prompt files to find conflicts."""
794
- quiet = ctx.obj.get("quiet", False)
795
- command_name = "conflicts"
796
- try:
797
- conflicts_list, total_cost, model_name = conflicts_main(
798
- ctx=ctx,
799
- prompt1=prompt1,
800
- prompt2=prompt2,
801
- output=output,
802
- )
803
- return conflicts_list, total_cost, model_name
804
- except Exception as e:
805
- handle_error(e, command_name, quiet)
806
- return None # Return None on failure
807
-
808
-
809
- @cli.command("crash")
810
- @click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False))
811
- @click.argument("code_file", type=click.Path(exists=True, dir_okay=False))
812
- @click.argument("program_file", type=click.Path(exists=True, dir_okay=False))
813
- @click.argument("error_file", type=click.Path(dir_okay=False)) # Allow non-existent
814
- @click.option(
815
- "--output", # Corresponds to output_code in crash_main
816
- type=click.Path(writable=True),
817
- default=None,
818
- help="Specify where to save the fixed code file (file or directory).",
819
- )
820
- @click.option(
821
- "--output-program",
822
- type=click.Path(writable=True),
823
- default=None,
824
- help="Specify where to save the fixed program file (file or directory).",
825
- )
826
- @click.option("--loop", is_flag=True, default=False, help="Enable iterative fixing process.")
827
- @click.option(
828
- "--max-attempts",
829
- type=int,
830
- default=3,
831
- show_default=True,
832
- help="Maximum number of fix attempts.",
833
- )
834
- @click.option(
835
- "--budget",
836
- type=float,
837
- default=5.0,
838
- show_default=True,
839
- help="Maximum cost allowed for the fixing process.",
840
- )
841
- @click.pass_context
842
- @track_cost
843
- def crash(
844
- ctx: click.Context,
845
- prompt_file: str,
846
- code_file: str,
847
- program_file: str,
848
- error_file: str,
849
- output: Optional[str], # Maps to output_code
850
- output_program: Optional[str],
851
- loop: bool,
852
- max_attempts: int,
853
- budget: float,
854
- ) -> Optional[Tuple[Dict[str, Any], float, str]]: # Modified return type
855
- """Fix errors in a code module and calling program that caused a crash."""
856
- quiet = ctx.obj.get("quiet", False)
857
- command_name = "crash"
858
- try:
859
- success, fixed_code, fixed_program, attempts, cost, model = crash_main(
860
- ctx=ctx,
861
- prompt_file=prompt_file,
862
- code_file=code_file,
863
- program_file=program_file,
864
- error_file=error_file,
865
- output=output,
866
- output_program=output_program,
867
- loop=loop,
868
- max_attempts=max_attempts,
869
- budget=budget,
870
- )
871
- result_data = {
872
- "success": success,
873
- "attempts": attempts,
874
- "fixed_code": fixed_code,
875
- "fixed_program": fixed_program,
876
- }
877
- return result_data, cost, model
878
- except Exception as e:
879
- handle_error(e, command_name, quiet)
880
- return None # Return None on failure
881
-
882
-
883
- @cli.command("trace")
884
- @click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False))
885
- @click.argument("code_file", type=click.Path(exists=True, dir_okay=False))
886
- @click.argument("code_line", type=int)
887
- @click.option(
888
- "--output",
889
- type=click.Path(writable=True),
890
- default=None,
891
- help="Specify where to save the trace analysis results log (file or directory).",
892
- )
893
- @click.pass_context
894
- @track_cost
895
- def trace(
896
- ctx: click.Context,
897
- prompt_file: str,
898
- code_file: str,
899
- code_line: int,
900
- output: Optional[str],
901
- ) -> Optional[Tuple[int | str, float, str]]: # Modified return type
902
- """Find the associated line number between a prompt file and generated code."""
903
- quiet = ctx.obj.get("quiet", False)
904
- command_name = "trace"
905
- try:
906
- prompt_line_result, total_cost, model_name = trace_main(
907
- ctx=ctx,
908
- prompt_file=prompt_file,
909
- code_file=code_file,
910
- code_line=code_line,
911
- output=output,
912
- )
913
- # Check if trace_main indicated failure (e.g., by returning None or specific error)
914
- # This depends on trace_main's implementation; assuming it raises exceptions for now.
915
- 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
916
- # Optionally handle specific non-exception failures differently if needed
917
- # For now, rely on exceptions being raised for errors like out-of-range.
918
- pass
919
- return prompt_line_result, total_cost, model_name
920
- except Exception as e:
921
- handle_error(e, command_name, quiet)
922
- # Exit with non-zero status code on any exception
923
- ctx.exit(1)
924
-
925
-
926
- @cli.command("bug")
927
- @click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False))
928
- @click.argument("code_file", type=click.Path(exists=True, dir_okay=False))
929
- @click.argument("program_file", type=click.Path(exists=True, dir_okay=False))
930
- @click.argument("current_output_file", type=click.Path(exists=True, dir_okay=False))
931
- @click.argument("desired_output_file", type=click.Path(exists=True, dir_okay=False))
932
- @click.option(
933
- "--output",
934
- type=click.Path(writable=True),
935
- default=None,
936
- help="Specify where to save the generated unit test (file or directory).",
937
- )
938
- @click.option("--language", type=str, default=None, help="Specify the programming language (default: Python).")
939
- @click.pass_context
940
- @track_cost
941
- def bug(
942
- ctx: click.Context,
943
- prompt_file: str,
944
- code_file: str,
945
- program_file: str,
946
- current_output_file: str,
947
- desired_output_file: str,
948
- output: Optional[str],
949
- language: Optional[str],
950
- ) -> Optional[Tuple[str, float, str]]: # Modified return type
951
- """Generate a unit test based on observed and desired outputs."""
952
- quiet = ctx.obj.get("quiet", False)
953
- command_name = "bug"
954
- try:
955
- unit_test_content, total_cost, model_name = bug_main(
956
- ctx=ctx,
957
- prompt_file=prompt_file,
958
- code_file=code_file,
959
- program_file=program_file,
960
- current_output=current_output_file,
961
- desired_output=desired_output_file,
962
- output=output,
963
- language=language,
964
- )
965
- return unit_test_content, total_cost, model_name
966
- except Exception as e:
967
- handle_error(e, command_name, quiet)
968
- return None # Return None on failure
969
-
970
-
971
- @cli.command("auto-deps")
972
- @click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False))
973
- @click.argument("directory_path", type=str) # Path with potential glob pattern
974
- @click.option(
975
- "--output",
976
- type=click.Path(writable=True),
977
- default=None,
978
- help="Specify where to save the modified prompt file (file or directory).",
979
- )
980
- @click.option(
981
- "--csv",
982
- "auto_deps_csv_path",
983
- type=click.Path(dir_okay=False), # CSV path is a file
984
- default=None, # Default handled by auto_deps_main or env var
985
- help="Specify the CSV file for dependency info (default: project_dependencies.csv or PDD_AUTO_DEPS_CSV_PATH).",
986
- )
987
- @click.option(
988
- "--force-scan",
989
- is_flag=True,
990
- default=False,
991
- help="Force rescanning of all potential dependency files.",
992
- )
993
- @click.pass_context
994
- @track_cost
995
- def auto_deps(
996
- ctx: click.Context,
997
- prompt_file: str,
998
- directory_path: str,
999
- output: Optional[str],
1000
- auto_deps_csv_path: Optional[str],
1001
- force_scan: bool,
1002
- ) -> Optional[Tuple[str, float, str]]: # Modified return type
1003
- """Analyze prompt and insert dependencies from a directory."""
1004
- quiet = ctx.obj.get("quiet", False)
1005
- command_name = "auto-deps"
1006
- try:
1007
- clean_directory_path = directory_path.strip('\"')
1008
-
1009
- modified_prompt, total_cost, model_name = auto_deps_main(
1010
- ctx=ctx,
1011
- prompt_file=prompt_file,
1012
- directory_path=clean_directory_path,
1013
- auto_deps_csv_path=auto_deps_csv_path,
1014
- output=output,
1015
- force_scan=force_scan,
1016
- )
1017
- return modified_prompt, total_cost, model_name
1018
- except Exception as e:
1019
- handle_error(e, command_name, quiet)
1020
- return None # Return None on failure
1021
-
1022
-
1023
- @cli.command("verify")
1024
- @click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False))
1025
- @click.argument("code_file", type=click.Path(exists=True, dir_okay=False))
1026
- @click.argument("program_file", type=click.Path(exists=True, dir_okay=False))
1027
- @click.option(
1028
- "--output-results",
1029
- type=click.Path(writable=True),
1030
- default=None,
1031
- help="Specify where to save the verification results log (file or directory).",
1032
- )
1033
- @click.option(
1034
- "--output-code",
1035
- type=click.Path(writable=True),
1036
- default=None,
1037
- help="Specify where to save the verified code file (file or directory).",
1038
- )
1039
- @click.option(
1040
- "--output-program",
1041
- type=click.Path(writable=True),
1042
- default=None,
1043
- help="Specify where to save the verified program file (file or directory).",
1044
- )
1045
- @click.option(
1046
- "--max-attempts",
1047
- type=int,
1048
- default=3,
1049
- show_default=True,
1050
- help="Maximum number of fix attempts within the verification loop.",
1051
- )
1052
- @click.option(
1053
- "--budget",
1054
- type=float,
1055
- default=5.0,
1056
- show_default=True,
1057
- help="Maximum cost allowed for the verification and fixing process.",
1058
- )
1059
- @click.pass_context
1060
- @track_cost
1061
- def verify(
1062
- ctx: click.Context,
1063
- prompt_file: str,
1064
- code_file: str,
1065
- program_file: str,
1066
- output_results: Optional[str],
1067
- output_code: Optional[str],
1068
- output_program: Optional[str],
1069
- max_attempts: int,
1070
- budget: float,
1071
- ) -> Optional[Tuple[Dict[str, Any], float, str]]: # Modified return type
1072
- """Verify code correctness against prompt using LLM judgment."""
1073
- quiet = ctx.obj.get("quiet", False)
1074
- command_name = "verify"
1075
- try:
1076
- success, final_program, final_code, attempts, total_cost_value, model_name_value = fix_verification_main(
1077
- ctx=ctx,
1078
- prompt_file=prompt_file,
1079
- code_file=code_file,
1080
- program_file=program_file,
1081
- output_results=output_results,
1082
- output_code=output_code,
1083
- output_program=output_program,
1084
- loop=True,
1085
- verification_program=program_file,
1086
- max_attempts=max_attempts,
1087
- budget=budget,
1088
- )
1089
- result_data = {
1090
- "success": success,
1091
- "attempts": attempts,
1092
- "verified_code_path": output_code,
1093
- "verified_program_path": output_program,
1094
- "results_log_path": output_results,
1095
- }
1096
- return result_data, total_cost_value, model_name_value
1097
- except Exception as e:
1098
- handle_error(e, command_name, quiet)
1099
- return None # Return None on failure
1100
-
1101
-
1102
- @cli.command("sync")
1103
- @click.argument("basename", type=str)
1104
- @click.option(
1105
- "--max-attempts",
1106
- type=int,
1107
- default=3,
1108
- show_default=True,
1109
- help="Maximum number of sync attempts.",
1110
- )
1111
- @click.option(
1112
- "--budget",
1113
- type=float,
1114
- default=10.0,
1115
- show_default=True,
1116
- help="Maximum total cost allowed for the entire sync process.",
1117
- )
1118
- @click.option(
1119
- "--skip-verify",
1120
- is_flag=True,
1121
- default=False,
1122
- help="Skip verification step during sync.",
1123
- )
1124
- @click.option(
1125
- "--skip-tests",
1126
- is_flag=True,
1127
- default=False,
1128
- help="Skip test generation during sync.",
1129
- )
1130
- @click.option(
1131
- "--target-coverage",
1132
- type=click.FloatRange(0.0, 100.0),
1133
- default=10.0,
1134
- show_default=True,
1135
- help="Target code coverage percentage for generated tests.",
1136
- )
1137
- @click.option(
1138
- "--log",
1139
- is_flag=True,
1140
- default=False,
1141
- help="Enable detailed logging during sync.",
1142
- )
1143
- @click.pass_context
1144
- @track_cost
1145
- def sync(
1146
- ctx: click.Context,
1147
- basename: str,
1148
- max_attempts: int,
1149
- budget: float,
1150
- skip_verify: bool,
1151
- skip_tests: bool,
1152
- target_coverage: float,
1153
- log: bool,
1154
- ) -> Optional[Tuple[Dict[str, Any], float, str]]:
1155
- """Automatically execute the complete PDD workflow loop for a given basename.
1156
-
1157
- This command implements the entire synchronized cycle, intelligently determining
1158
- what steps are needed and executing them in the correct order. It detects
1159
- programming languages by scanning for prompt files matching the pattern
1160
- {basename}_{language}.prompt in the prompts directory.
1161
- """
1162
- try:
1163
- results, total_cost, model = sync_main(
1164
- ctx=ctx,
1165
- basename=basename,
1166
- max_attempts=max_attempts,
1167
- budget=budget,
1168
- skip_verify=skip_verify,
1169
- skip_tests=skip_tests,
1170
- target_coverage=target_coverage,
1171
- log=log,
1172
- )
1173
- return results, total_cost, model
1174
- except Exception as exception:
1175
- handle_error(exception, "sync", ctx.obj.get("quiet", False))
1176
- return None
1177
-
1178
-
1179
- @cli.command("install_completion")
1180
- @click.pass_context
1181
- # No @track_cost
1182
- def install_completion_cmd(ctx: click.Context) -> None: # Return type remains None
1183
- """Install shell completion for the PDD CLI."""
1184
- command_name = "install_completion" # For error handling
1185
- quiet_mode = ctx.obj.get("quiet", False) # Get quiet from context
1186
-
1187
- try:
1188
- # The actual install_completion function is imported from .install_completion
1189
- install_completion(quiet=quiet_mode) # Pass quiet_mode
1190
- # Success messages are handled within install_completion based on quiet_mode
1191
- # No need to print additional messages here unless specifically required
1192
- # if not quiet_mode:
1193
- # console.print(f"[success]'{command_name}' command completed successfully.[/success]")
1194
- except Exception as e:
1195
- # Use the centralized error handler
1196
- handle_error(e, command_name, quiet_mode)
1197
- # Do not return anything, as the callback expects None or a tuple
1198
-
33
+ from .preprocess_main import preprocess_main
34
+ from .construct_paths import construct_paths
35
+ from .fix_verification_main import fix_verification_main
36
+ from .core.errors import console
37
+ from .install_completion import install_completion
38
+ from .core.utils import _should_show_onboarding_reminder
39
+ from .core.cli import process_commands
40
+ import subprocess
1199
41
 
1200
- # --- Entry Point ---
1201
42
  if __name__ == "__main__":
1202
43
  cli()