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
@@ -3,13 +3,18 @@ import os
3
3
  import subprocess
4
4
  import click
5
5
  import logging
6
+ import json
7
+ from pathlib import Path
6
8
  from typing import Optional, Tuple, List, Dict, Any
7
9
 
10
+ import requests
11
+
8
12
  # Use Rich for pretty printing to the console
9
13
  from rich import print as rich_print
10
14
  from rich.panel import Panel
11
15
  from rich.syntax import Syntax
12
16
  from rich.text import Text
17
+ from rich.console import Console
13
18
 
14
19
  # Internal imports using relative paths
15
20
  from .construct_paths import construct_paths
@@ -18,14 +23,27 @@ from .fix_verification_errors_loop import fix_verification_errors_loop
18
23
  # Import DEFAULT_STRENGTH from the main package
19
24
  from . import DEFAULT_STRENGTH, DEFAULT_TIME
20
25
  from .python_env_detector import detect_host_python_executable
26
+ from .core.cloud import CloudConfig
21
27
 
22
28
  # Default values from the README
23
29
  DEFAULT_TEMPERATURE = 0.0
24
30
  DEFAULT_MAX_ATTEMPTS = 3
25
31
  DEFAULT_BUDGET = 5.0
26
32
 
33
+ # Cloud request timeout
34
+ CLOUD_REQUEST_TIMEOUT = 400 # seconds
35
+
27
36
  # Configure logging
28
37
  logger = logging.getLogger(__name__)
38
+ console = Console()
39
+
40
+
41
+ def _env_flag_enabled(name: str) -> bool:
42
+ """Return True when an env var is set to a truthy value."""
43
+ value = os.environ.get(name)
44
+ if value is None:
45
+ return False
46
+ return str(value).strip().lower() in {"1", "true", "yes", "on"}
29
47
 
30
48
  # Define a constant for the verification program name
31
49
  VERIFICATION_PROGRAM_NAME = "verification_program.py" # Example, adjust if needed
@@ -116,6 +134,9 @@ def fix_verification_main(
116
134
  verification_program: Optional[str], # Only used if loop=True
117
135
  max_attempts: int = DEFAULT_MAX_ATTEMPTS,
118
136
  budget: float = DEFAULT_BUDGET,
137
+ agentic_fallback: bool = True,
138
+ strength: Optional[float] = None,
139
+ temperature: Optional[float] = None,
119
140
  ) -> Tuple[bool, str, str, int, float, str]:
120
141
  """
121
142
  CLI wrapper for the 'verify' command. Verifies code correctness by running
@@ -144,9 +165,9 @@ def fix_verification_main(
144
165
  - total_cost (float): Total cost incurred.
145
166
  - model_name (str): Name of the LLM used.
146
167
  """
147
- # Extract global options from context
148
- strength: float = ctx.obj.get('strength', DEFAULT_STRENGTH)
149
- temperature: float = ctx.obj.get('temperature', DEFAULT_TEMPERATURE)
168
+ # Extract global options from context (prefer passed parameters over ctx.obj)
169
+ strength: float = strength if strength is not None else ctx.obj.get('strength', DEFAULT_STRENGTH)
170
+ temperature: float = temperature if temperature is not None else ctx.obj.get('temperature', DEFAULT_TEMPERATURE)
150
171
  force: bool = ctx.obj.get('force', False)
151
172
  quiet: bool = ctx.obj.get('quiet', False)
152
173
  verbose: bool = ctx.obj.get('verbose', False)
@@ -204,6 +225,8 @@ def fix_verification_main(
204
225
  quiet=quiet,
205
226
  command="verify",
206
227
  command_options=command_options,
228
+ context_override=ctx.obj.get('context'),
229
+ confirm_callback=ctx.obj.get('confirm_callback')
207
230
  )
208
231
  output_code_path = output_file_paths.get("output_code")
209
232
  output_results_path = output_file_paths.get("output_results")
@@ -212,6 +235,9 @@ def fix_verification_main(
212
235
  if verbose:
213
236
  rich_print("[dim]Resolved output paths via construct_paths.[/dim]")
214
237
 
238
+ except click.Abort:
239
+ # User cancelled - re-raise to stop the sync loop
240
+ raise
215
241
  except Exception as e:
216
242
  # If the helper does not understand the "verify" command fall back.
217
243
  if "invalid command" in str(e).lower():
@@ -230,7 +256,8 @@ def fix_verification_main(
230
256
  input_strings["program_file"] = f.read()
231
257
  except FileNotFoundError as fe:
232
258
  rich_print(f"[bold red]Error:[/bold red] {fe}")
233
- sys.exit(1)
259
+ # Return error result instead of sys.exit(1) to allow orchestrator to handle gracefully
260
+ return False, "", "", 0, 0.0, f"FileNotFoundError: {fe}"
234
261
 
235
262
  # Pick or build output paths
236
263
  if output_code_path is None:
@@ -252,7 +279,8 @@ def fix_verification_main(
252
279
  if verbose:
253
280
  import traceback
254
281
  rich_print(Panel(traceback.format_exc(), title="Traceback", border_style="red"))
255
- sys.exit(1)
282
+ # Return error result instead of sys.exit(1) to allow orchestrator to handle gracefully
283
+ return False, "", "", 0, 0.0, f"Error: {e}"
256
284
 
257
285
  # --- Core Logic ---
258
286
  success: bool = False
@@ -263,28 +291,55 @@ def fix_verification_main(
263
291
  model_name: str = "N/A"
264
292
  results_log_content: str = ""
265
293
 
294
+ # Determine cloud vs local execution preference
295
+ is_local_execution_preferred = ctx.obj.get('local', False)
296
+ cloud_only = _env_flag_enabled("PDD_CLOUD_ONLY") or _env_flag_enabled("PDD_NO_LOCAL_FALLBACK")
297
+ current_execution_is_local = is_local_execution_preferred and not cloud_only
298
+
299
+ # Cloud execution tracking
300
+ cloud_execution_attempted = False
301
+ cloud_execution_succeeded = False
302
+
266
303
  try:
267
304
  if loop:
305
+ # Determine if loop should use cloud for LLM calls (hybrid mode)
306
+ # Local verification execution stays local, but LLM fix calls can go to cloud
307
+ use_cloud_for_loop = not is_local_execution_preferred and not cloud_only
308
+
309
+ # If cloud_only is set but we're in loop mode, we still use hybrid approach
310
+ if cloud_only and not is_local_execution_preferred:
311
+ use_cloud_for_loop = True
312
+
313
+ if verbose:
314
+ mode_desc = "hybrid (local execution + cloud LLM)" if use_cloud_for_loop else "local"
315
+ console.print(Panel(f"Performing {mode_desc} verification loop...", title="[blue]Mode[/blue]", expand=False))
316
+
268
317
  if not quiet:
269
318
  rich_print("[dim]Running Iterative Verification (fix_verification_errors_loop)...[/dim]")
270
319
  try:
320
+ # Build kwargs for fix_verification_errors_loop
321
+ loop_kwargs = {
322
+ "program_file": program_file,
323
+ "code_file": code_file,
324
+ "prompt": input_strings["prompt_file"],
325
+ "prompt_file": prompt_file,
326
+ "verification_program": verification_program,
327
+ "strength": strength,
328
+ "temperature": temperature,
329
+ "llm_time": time,
330
+ "max_attempts": max_attempts,
331
+ "budget": budget,
332
+ "verification_log_file": output_results_path,
333
+ "verbose": verbose,
334
+ "program_args": [],
335
+ "agentic_fallback": agentic_fallback,
336
+ }
337
+ # Only pass use_cloud when explicitly True (cloud not ready for prod yet)
338
+ if use_cloud_for_loop:
339
+ loop_kwargs["use_cloud"] = True
340
+
271
341
  # Call fix_verification_errors_loop for iterative fixing
272
- loop_results = fix_verification_errors_loop(
273
- program_file=program_file, # Changed to pass the program_file path
274
- code_file=code_file, # Changed to pass the code_file path
275
- prompt=input_strings["prompt_file"], # Correctly passing prompt content
276
- verification_program=verification_program, # Path to the verifier program
277
- strength=strength,
278
- temperature=temperature,
279
- llm_time=time, # Changed 'time' to 'llm_time'
280
- max_attempts=max_attempts,
281
- budget=budget,
282
- verification_log_file=output_results_path, # Use resolved output_results_path
283
- # output_code_path should not be passed here
284
- # output_program_path should not be passed here
285
- verbose=verbose,
286
- program_args=[] # Pass an empty list for program_args
287
- )
342
+ loop_results = fix_verification_errors_loop(**loop_kwargs)
288
343
  success = loop_results.get('success', False)
289
344
  final_program = loop_results.get('final_program', "") # Use .get for safety
290
345
  final_code = loop_results.get('final_code', "") # Use .get for safety
@@ -304,7 +359,7 @@ def fix_verification_main(
304
359
  rich_print("\n[bold blue]Running Single Pass Verification (fix_verification_errors)...[/bold blue]")
305
360
  attempts = 1 # Single pass is one attempt
306
361
 
307
- # 1. Run the program file to get its output
362
+ # 1. Run the program file to get its output (always local)
308
363
  if not quiet:
309
364
  rich_print(f"Executing program: [cyan]{program_file}[/cyan]")
310
365
  run_success, program_stdout, program_stderr = run_program(program_file)
@@ -315,71 +370,215 @@ def fix_verification_main(
315
370
  rich_print(Panel(program_output if program_output else "[No Output]", border_style="dim"))
316
371
  rich_print("[dim]--- End Program Output ---[/dim]")
317
372
 
318
- # Check if program ran successfully before calling LLM (optional, but good practice)
319
- # if not run_success:
320
- # rich_print("[yellow]Warning:[/yellow] Program execution failed. LLM verification might be less effective.")
321
-
322
- # 2. Call fix_verification_errors with content and program output
323
- if not quiet:
324
- rich_print("Calling LLM to verify program output against prompt...")
325
- fix_results = fix_verification_errors(
326
- program=input_strings["program_file"],
327
- prompt=input_strings["prompt_file"],
328
- code=input_strings["code_file"],
329
- output=program_output,
330
- strength=strength,
331
- temperature=temperature,
332
- verbose=verbose,
333
- time=time # Pass time to single pass function
334
- )
335
-
336
- # Determine success: If no issues were found OR if fixes were applied
337
- # The definition of 'success' here means the *final* state is verified.
338
- issues_found = fix_results['verification_issues_count'] > 0
339
- code_updated = fix_results['fixed_code'] != input_strings["code_file"]
340
- program_updated = fix_results['fixed_program'] != input_strings["program_file"]
341
-
342
- if not issues_found:
343
- success = True
344
- if not quiet: rich_print("[green]Verification Passed:[/green] LLM found no discrepancies.")
345
- elif code_updated or program_updated:
346
- # If issues were found AND fixes were made, assume success for this single pass.
347
- # A more robust check might re-run the program with fixed code, but that's the loop's job.
348
- success = True
349
- if not quiet: rich_print("[yellow]Verification Issues Found:[/yellow] LLM proposed fixes.")
350
- else:
351
- success = False
352
- if not quiet: rich_print("[red]Verification Failed:[/red] LLM found discrepancies but proposed no fixes.")
353
-
354
- final_program = fix_results['fixed_program']
355
- final_code = fix_results['fixed_code']
356
- total_cost = fix_results['total_cost']
357
- model_name = fix_results['model_name']
358
-
359
- # Build results log content for single pass
360
- results_log_content = "PDD Verify Results (Single Pass)\n"
361
- results_log_content += f"Timestamp: {os.path.getmtime(prompt_file)}\n"
362
- results_log_content += f"Prompt File: {prompt_file}\n"
363
- results_log_content += f"Code File: {code_file}\n"
364
- results_log_content += f"Program File: {program_file}\n"
365
- results_log_content += f"Success: {success}\n"
366
- results_log_content += f"Issues Found Count: {fix_results['verification_issues_count']}\n"
367
- results_log_content += f"Code Updated: {code_updated}\n"
368
- results_log_content += f"Program Updated: {program_updated}\n"
369
- results_log_content += f"Model Used: {model_name}\n"
370
- results_log_content += f"Total Cost: ${total_cost:.6f}\n"
371
- results_log_content += "\n--- LLM Explanation ---\n"
372
- # The original code here was:
373
- # results_log_content += "\n".join(fix_results.get('explanation', ['N/A']))
374
- # This was incorrect because fix_results['explanation'] is a single string.
375
- # The list constructor would then iterate through it character-by-character,
376
- # causing the single-character-per-line output.
377
- # The fix is to just append the string directly, using a default value if it is None.
378
- results_log_content += fix_results.get('explanation') or 'N/A'
379
- results_log_content += "\n\n--- Program Output Used for Verification ---\n"
380
- results_log_content += program_output
381
-
373
+ # 2. Attempt cloud verification first if not local preferred
374
+ if not current_execution_is_local:
375
+ if verbose:
376
+ console.print(Panel("Attempting cloud verification execution...", title="[blue]Mode[/blue]", expand=False))
377
+
378
+ jwt_token = CloudConfig.get_jwt_token(verbose=verbose)
379
+
380
+ if not jwt_token:
381
+ if cloud_only:
382
+ console.print("[red]Cloud authentication failed.[/red]")
383
+ raise click.UsageError("Cloud authentication failed")
384
+ console.print("[yellow]Cloud authentication failed. Falling back to local execution.[/yellow]")
385
+ current_execution_is_local = True
386
+
387
+ if jwt_token and not current_execution_is_local:
388
+ cloud_execution_attempted = True
389
+ # Build cloud payload
390
+ payload = {
391
+ "programContent": input_strings["program_file"],
392
+ "promptContent": input_strings["prompt_file"],
393
+ "codeContent": input_strings["code_file"],
394
+ "outputContent": program_output,
395
+ "language": language,
396
+ "strength": strength,
397
+ "temperature": temperature,
398
+ "time": time if time is not None else 0.25,
399
+ "verbose": verbose,
400
+ }
401
+
402
+ headers = {
403
+ "Authorization": f"Bearer {jwt_token}",
404
+ "Content-Type": "application/json"
405
+ }
406
+ cloud_url = CloudConfig.get_endpoint_url("verifyCode")
407
+
408
+ try:
409
+ response = requests.post(
410
+ cloud_url,
411
+ json=payload,
412
+ headers=headers,
413
+ timeout=CLOUD_REQUEST_TIMEOUT
414
+ )
415
+ response.raise_for_status()
416
+
417
+ response_data = response.json()
418
+ fixed_code = response_data.get("fixedCode", "")
419
+ fixed_program = response_data.get("fixedProgram", "")
420
+ explanation = response_data.get("explanation", "")
421
+ issues_count = response_data.get("issuesCount", 0)
422
+ total_cost = float(response_data.get("totalCost", 0.0))
423
+ model_name = response_data.get("modelName", "cloud_model")
424
+
425
+ cloud_execution_succeeded = True
426
+
427
+ # Determine success based on issues count
428
+ code_updated = fixed_code != input_strings["code_file"]
429
+ program_updated = fixed_program != input_strings["program_file"]
430
+
431
+ if issues_count == 0:
432
+ success = True
433
+ if not quiet: rich_print("[green]Verification Passed:[/green] Cloud found no discrepancies.")
434
+ elif code_updated or program_updated:
435
+ success = True
436
+ if not quiet: rich_print("[yellow]Verification Issues Found:[/yellow] Cloud proposed fixes.")
437
+ else:
438
+ success = False
439
+ if not quiet: rich_print("[red]Verification Failed:[/red] Cloud found discrepancies but proposed no fixes.")
440
+
441
+ final_program = fixed_program
442
+ final_code = fixed_code
443
+
444
+ # Build results log content for cloud execution
445
+ results_log_content = "PDD Verify Results (Cloud Single Pass)\n"
446
+ results_log_content += f"Prompt File: {prompt_file}\n"
447
+ results_log_content += f"Code File: {code_file}\n"
448
+ results_log_content += f"Program File: {program_file}\n"
449
+ results_log_content += f"Success: {success}\n"
450
+ results_log_content += f"Issues Found Count: {issues_count}\n"
451
+ results_log_content += f"Code Updated: {code_updated}\n"
452
+ results_log_content += f"Program Updated: {program_updated}\n"
453
+ results_log_content += f"Model Used: {model_name}\n"
454
+ results_log_content += f"Total Cost: ${total_cost:.6f}\n"
455
+ results_log_content += "\n--- LLM Explanation ---\n"
456
+ results_log_content += explanation or 'N/A'
457
+ results_log_content += "\n\n--- Program Output Used for Verification ---\n"
458
+ results_log_content += program_output
459
+
460
+ if verbose:
461
+ console.print(Panel(
462
+ f"Cloud verification completed. Model: {model_name}, Cost: ${total_cost:.6f}",
463
+ title="[green]Cloud Success[/green]",
464
+ expand=False
465
+ ))
466
+
467
+ except requests.exceptions.Timeout:
468
+ if cloud_only:
469
+ console.print(f"[red]Cloud execution timed out ({CLOUD_REQUEST_TIMEOUT}s).[/red]")
470
+ raise click.UsageError("Cloud execution timed out")
471
+ console.print(f"[yellow]Cloud execution timed out ({CLOUD_REQUEST_TIMEOUT}s). Falling back to local.[/yellow]")
472
+ current_execution_is_local = True
473
+
474
+ except requests.exceptions.HTTPError as e:
475
+ status_code = e.response.status_code if e.response else 0
476
+ err_content = e.response.text[:200] if e.response else "No response content"
477
+
478
+ # Non-recoverable errors: do NOT fall back to local
479
+ if status_code == 402: # Insufficient credits
480
+ try:
481
+ error_data = e.response.json()
482
+ current_balance = error_data.get("currentBalance", "unknown")
483
+ estimated_cost = error_data.get("estimatedCost", "unknown")
484
+ console.print(f"[red]Insufficient credits. Current balance: {current_balance}, estimated cost: {estimated_cost}[/red]")
485
+ except Exception:
486
+ console.print(f"[red]Insufficient credits: {err_content}[/red]")
487
+ raise click.UsageError("Insufficient credits for cloud verification")
488
+ elif status_code == 401: # Authentication error
489
+ console.print(f"[red]Authentication failed: {err_content}[/red]")
490
+ raise click.UsageError("Cloud authentication failed")
491
+ elif status_code == 403: # Authorization error (not approved)
492
+ console.print(f"[red]Access denied: {err_content}[/red]")
493
+ raise click.UsageError("Access denied - user not approved")
494
+ elif status_code == 400: # Validation error
495
+ console.print(f"[red]Invalid request: {err_content}[/red]")
496
+ raise click.UsageError(f"Invalid request: {err_content}")
497
+ else:
498
+ # Recoverable errors (5xx, unexpected errors): fall back to local
499
+ if cloud_only:
500
+ console.print(f"[red]Cloud HTTP error ({status_code}): {err_content}[/red]")
501
+ raise click.UsageError(f"Cloud HTTP error ({status_code}): {err_content}")
502
+ console.print(f"[yellow]Cloud HTTP error ({status_code}): {err_content}. Falling back to local.[/yellow]")
503
+ current_execution_is_local = True
504
+
505
+ except requests.exceptions.RequestException as e:
506
+ if cloud_only:
507
+ console.print(f"[red]Cloud network error: {e}[/red]")
508
+ raise click.UsageError(f"Cloud network error: {e}")
509
+ console.print(f"[yellow]Cloud network error: {e}. Falling back to local.[/yellow]")
510
+ current_execution_is_local = True
511
+
512
+ except json.JSONDecodeError:
513
+ if cloud_only:
514
+ console.print("[red]Cloud returned invalid JSON.[/red]")
515
+ raise click.UsageError("Cloud returned invalid JSON")
516
+ console.print("[yellow]Cloud returned invalid JSON. Falling back to local.[/yellow]")
517
+ current_execution_is_local = True
518
+
519
+ # Local execution path (when cloud failed/skipped or local preferred)
520
+ if not cloud_execution_succeeded:
521
+ if verbose:
522
+ console.print(Panel("Performing local verification...", title="[blue]Mode[/blue]", expand=False))
523
+
524
+ # Call fix_verification_errors with content and program output
525
+ if not quiet:
526
+ rich_print("Calling LLM to verify program output against prompt...")
527
+ fix_results = fix_verification_errors(
528
+ program=input_strings["program_file"],
529
+ prompt=input_strings["prompt_file"],
530
+ code=input_strings["code_file"],
531
+ output=program_output,
532
+ strength=strength,
533
+ temperature=temperature,
534
+ verbose=verbose,
535
+ time=time # Pass time to single pass function
536
+ )
382
537
 
538
+ # Determine success: If no issues were found OR if fixes were applied
539
+ # The definition of 'success' here means the *final* state is verified.
540
+ issues_found = fix_results['verification_issues_count'] > 0
541
+ code_updated = fix_results['fixed_code'] != input_strings["code_file"]
542
+ program_updated = fix_results['fixed_program'] != input_strings["program_file"]
543
+
544
+ if not issues_found:
545
+ success = True
546
+ if not quiet: rich_print("[green]Verification Passed:[/green] LLM found no discrepancies.")
547
+ elif code_updated or program_updated:
548
+ # If issues were found AND fixes were made, assume success for this single pass.
549
+ # A more robust check might re-run the program with fixed code, but that's the loop's job.
550
+ success = True
551
+ if not quiet: rich_print("[yellow]Verification Issues Found:[/yellow] LLM proposed fixes.")
552
+ else:
553
+ success = False
554
+ if not quiet: rich_print("[red]Verification Failed:[/red] LLM found discrepancies but proposed no fixes.")
555
+
556
+ final_program = fix_results['fixed_program']
557
+ final_code = fix_results['fixed_code']
558
+ total_cost = fix_results['total_cost']
559
+ model_name = fix_results['model_name']
560
+
561
+ # Build results log content for single pass
562
+ results_log_content = "PDD Verify Results (Single Pass)\n"
563
+ results_log_content += f"Timestamp: {os.path.getmtime(prompt_file)}\n"
564
+ results_log_content += f"Prompt File: {prompt_file}\n"
565
+ results_log_content += f"Code File: {code_file}\n"
566
+ results_log_content += f"Program File: {program_file}\n"
567
+ results_log_content += f"Success: {success}\n"
568
+ results_log_content += f"Issues Found Count: {fix_results['verification_issues_count']}\n"
569
+ results_log_content += f"Code Updated: {code_updated}\n"
570
+ results_log_content += f"Program Updated: {program_updated}\n"
571
+ results_log_content += f"Model Used: {model_name}\n"
572
+ results_log_content += f"Total Cost: ${total_cost:.6f}\n"
573
+ results_log_content += "\n--- LLM Explanation ---\n"
574
+ results_log_content += fix_results.get('explanation') or 'N/A'
575
+ results_log_content += "\n\n--- Program Output Used for Verification ---\n"
576
+ results_log_content += program_output
577
+
578
+
579
+ except click.UsageError:
580
+ # Re-raise UsageError for proper CLI handling (e.g., cloud auth failures, insufficient credits)
581
+ raise
383
582
  except Exception as e:
384
583
  success = False
385
584
  rich_print(f"[bold red]Error during verification process:[/bold red] {e}")
@@ -407,7 +606,9 @@ def fix_verification_main(
407
606
  try:
408
607
  if verbose:
409
608
  rich_print(f"[cyan bold DEBUG] In fix_verification_main, ATTEMPTING to write code to: {output_code_path!r}")
410
- with open(output_code_path, "w") as f:
609
+ output_code_path_obj = Path(output_code_path)
610
+ output_code_path_obj.parent.mkdir(parents=True, exist_ok=True)
611
+ with open(output_code_path_obj, "w") as f:
411
612
  f.write(final_code)
412
613
  saved_code_path = output_code_path
413
614
  if not quiet:
@@ -427,7 +628,9 @@ def fix_verification_main(
427
628
  try:
428
629
  if verbose:
429
630
  rich_print(f"[cyan bold DEBUG] In fix_verification_main, ATTEMPTING to write program to: {output_program_path!r}")
430
- with open(output_program_path, "w") as f:
631
+ output_program_path_obj = Path(output_program_path)
632
+ output_program_path_obj.parent.mkdir(parents=True, exist_ok=True)
633
+ with open(output_program_path_obj, "w") as f:
431
634
  f.write(final_program)
432
635
  saved_program_path = output_program_path
433
636
  if not quiet:
@@ -437,7 +640,9 @@ def fix_verification_main(
437
640
 
438
641
  if not loop and output_results_path:
439
642
  try:
440
- with open(output_results_path, "w") as f:
643
+ output_results_path_obj = Path(output_results_path)
644
+ output_results_path_obj.parent.mkdir(parents=True, exist_ok=True)
645
+ with open(output_results_path_obj, "w") as f:
441
646
  f.write(results_log_content)
442
647
  saved_results_path = output_results_path
443
648
  if not quiet:
@@ -0,0 +1 @@
1
+ .react-flow{direction:ltr}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1;cursor:-webkit-grab;cursor:grab}.react-flow__pane.selection{cursor:pointer}.react-flow__pane.dragging{cursor:-webkit-grabbing;cursor:grabbing}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow .react-flow__edges{pointer-events:none;overflow:visible}.react-flow__edge-path,.react-flow__connection-path{stroke:#b1b1b7;stroke-width:1;fill:none}.react-flow__edge{pointer-events:visibleStroke;cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;-webkit-animation:dashdraw .5s linear infinite;animation:dashdraw .5s linear infinite}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;-webkit-animation:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge:focus .react-flow__edge-path,.react-flow__edge:focus-visible .react-flow__edge-path{stroke:#555}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge-textbg{fill:#fff}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;-webkit-animation:dashdraw .5s linear infinite;animation:dashdraw .5s linear infinite}.react-flow__connectionline{z-index:1001}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{position:absolute;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:-webkit-grab;cursor:grab}.react-flow__node.dragging{cursor:-webkit-grabbing;cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:left top;pointer-events:none}.react-flow__nodesselection-rect{position:absolute;pointer-events:all;cursor:-webkit-grab;cursor:grab}.react-flow__handle{position:absolute;pointer-events:none;min-width:5px;min-height:5px;width:6px;height:6px;background:#1a192b;border:1px solid white;border-radius:100%}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;left:50%;bottom:-4px;transform:translate(-50%)}.react-flow__handle-top{left:50%;top:-4px;transform:translate(-50%)}.react-flow__handle-left{top:50%;left:-4px;transform:translateY(-50%)}.react-flow__handle-right{right:-4px;top:50%;transform:translateY(-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__panel{position:absolute;z-index:5;margin:15px}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.center{left:50%;transform:translate(-50%)}.react-flow__attribution{font-size:10px;background:#ffffff80;padding:2px 3px;margin:0}.react-flow__attribution a{text-decoration:none;color:#999}@-webkit-keyframes dashdraw{0%{stroke-dashoffset:10}}@keyframes dashdraw{0%{stroke-dashoffset:10}}.react-flow__edgelabel-renderer{position:absolute;width:100%;height:100%;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-default,.react-flow__node-input,.react-flow__node-output,.react-flow__node-group{padding:10px;border-radius:3px;width:150px;font-size:12px;color:#222;text-align:center;border-width:1px;border-style:solid;border-color:#1a192b;background-color:#fff}.react-flow__node-default.selectable:hover,.react-flow__node-input.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:0 1px 4px 1px #00000014}.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:0 0 0 .5px #1a192b}.react-flow__node-group{background-color:#f0f0f040}.react-flow__nodesselection-rect,.react-flow__selection{background:#0059dc14;border:1px dotted rgba(0,89,220,.8)}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls{box-shadow:0 0 2px 1px #00000014}.react-flow__controls-button{border:none;background:#fefefe;border-bottom:1px solid #eee;box-sizing:content-box;display:flex;justify-content:center;align-items:center;width:16px;height:16px;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;padding:5px}.react-flow__controls-button:hover{background:#f4f4f4}.react-flow__controls-button svg{width:100%;max-width:12px;max-height:12px}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__minimap{background-color:#fff}.react-flow__minimap svg{display:block}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{width:4px;height:4px;border:1px solid #fff;border-radius:1px;background-color:#3367d9;transform:translate(-50%,-50%)}.react-flow__resize-control.handle.left{left:0;top:50%}.react-flow__resize-control.handle.right{left:100%;top:50%}.react-flow__resize-control.handle.top{left:50%;top:0}.react-flow__resize-control.handle.bottom{left:50%;top:100%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:#3367d9;border-width:0;border-style:solid}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;transform:translate(-50%);top:0;height:100%}.react-flow__resize-control.line.left{left:0;border-left-width:1px}.react-flow__resize-control.line.right{left:100%;border-right-width:1px}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{height:1px;transform:translateY(-50%);left:0;width:100%}.react-flow__resize-control.line.top{top:0;border-top-width:1px}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}