pdd-cli 0.0.24__py3-none-any.whl → 0.0.26__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pdd-cli might be problematic. Click here for more details.

Files changed (49) hide show
  1. pdd/__init__.py +14 -1
  2. pdd/bug_main.py +5 -1
  3. pdd/bug_to_unit_test.py +16 -5
  4. pdd/change.py +2 -1
  5. pdd/change_main.py +407 -189
  6. pdd/cli.py +853 -301
  7. pdd/code_generator.py +2 -1
  8. pdd/conflicts_in_prompts.py +2 -1
  9. pdd/construct_paths.py +377 -222
  10. pdd/context_generator.py +2 -1
  11. pdd/continue_generation.py +5 -2
  12. pdd/crash_main.py +55 -20
  13. pdd/data/llm_model.csv +18 -17
  14. pdd/detect_change.py +2 -1
  15. pdd/fix_code_loop.py +465 -160
  16. pdd/fix_code_module_errors.py +7 -4
  17. pdd/fix_error_loop.py +9 -9
  18. pdd/fix_errors_from_unit_tests.py +207 -365
  19. pdd/fix_main.py +32 -4
  20. pdd/fix_verification_errors.py +148 -77
  21. pdd/fix_verification_errors_loop.py +842 -768
  22. pdd/fix_verification_main.py +412 -0
  23. pdd/generate_output_paths.py +427 -189
  24. pdd/generate_test.py +3 -2
  25. pdd/increase_tests.py +2 -2
  26. pdd/llm_invoke.py +1167 -343
  27. pdd/preprocess.py +3 -3
  28. pdd/process_csv_change.py +466 -154
  29. pdd/prompts/bug_to_unit_test_LLM.prompt +11 -11
  30. pdd/prompts/extract_prompt_update_LLM.prompt +11 -5
  31. pdd/prompts/extract_unit_code_fix_LLM.prompt +2 -2
  32. pdd/prompts/find_verification_errors_LLM.prompt +11 -9
  33. pdd/prompts/fix_code_module_errors_LLM.prompt +29 -0
  34. pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +5 -5
  35. pdd/prompts/fix_verification_errors_LLM.prompt +8 -1
  36. pdd/prompts/generate_test_LLM.prompt +9 -3
  37. pdd/prompts/trim_results_start_LLM.prompt +1 -1
  38. pdd/prompts/update_prompt_LLM.prompt +3 -3
  39. pdd/split.py +6 -5
  40. pdd/split_main.py +13 -4
  41. pdd/trace_main.py +7 -0
  42. pdd/update_model_costs.py +446 -0
  43. pdd/xml_tagger.py +2 -1
  44. {pdd_cli-0.0.24.dist-info → pdd_cli-0.0.26.dist-info}/METADATA +8 -16
  45. {pdd_cli-0.0.24.dist-info → pdd_cli-0.0.26.dist-info}/RECORD +49 -47
  46. {pdd_cli-0.0.24.dist-info → pdd_cli-0.0.26.dist-info}/WHEEL +1 -1
  47. {pdd_cli-0.0.24.dist-info → pdd_cli-0.0.26.dist-info}/entry_points.txt +0 -0
  48. {pdd_cli-0.0.24.dist-info → pdd_cli-0.0.26.dist-info}/licenses/LICENSE +0 -0
  49. {pdd_cli-0.0.24.dist-info → pdd_cli-0.0.26.dist-info}/top_level.txt +0 -0
pdd/fix_main.py CHANGED
@@ -2,10 +2,12 @@ import sys
2
2
  from typing import Tuple, Optional
3
3
  import click
4
4
  from rich import print as rprint
5
+ from rich.markup import MarkupError, escape
5
6
 
6
7
  import requests
7
8
  import asyncio
8
9
  import os
10
+ from pathlib import Path
9
11
 
10
12
  from .preprocess import preprocess
11
13
 
@@ -15,6 +17,9 @@ from .fix_error_loop import fix_error_loop
15
17
  from .get_jwt_token import get_jwt_token
16
18
  from .get_language import get_language
17
19
 
20
+ # Import DEFAULT_STRENGTH from the package
21
+ from . import DEFAULT_STRENGTH
22
+
18
23
  def fix_main(
19
24
  ctx: click.Context,
20
25
  prompt_file: str,
@@ -65,6 +70,12 @@ def fix_main(
65
70
  analysis_results = None
66
71
 
67
72
  try:
73
+ # Verify error file exists if not in loop mode
74
+ if not loop:
75
+ error_path = Path(error_file)
76
+ if not error_path.exists():
77
+ raise FileNotFoundError(f"Error file '{error_file}' does not exist.")
78
+
68
79
  # Construct file paths
69
80
  input_file_paths = {
70
81
  "prompt_file": prompt_file,
@@ -85,11 +96,12 @@ def fix_main(
85
96
  force=ctx.obj.get('force', False),
86
97
  quiet=ctx.obj.get('quiet', False),
87
98
  command="fix",
88
- command_options=command_options
99
+ command_options=command_options,
100
+ create_error_file=loop # Only create error file if in loop mode
89
101
  )
90
102
 
91
103
  # Get parameters from context
92
- strength = ctx.obj.get('strength', 0.9)
104
+ strength = ctx.obj.get('strength', DEFAULT_STRENGTH)
93
105
  temperature = ctx.obj.get('temperature', 0)
94
106
  verbose = ctx.obj.get('verbose', False)
95
107
  if loop:
@@ -139,7 +151,16 @@ def fix_main(
139
151
  if verbose and analysis_results:
140
152
  # Log the first 200 characters of analysis if in verbose mode
141
153
  analysis_preview = analysis_results[:200] + "..." if len(analysis_results) > 200 else analysis_results
142
- rprint(f"[bold]Analysis preview:[/bold] {analysis_preview}")
154
+ try:
155
+ # Attempt to print the preview using rich markup parsing
156
+ rprint(f"[bold]Analysis preview:[/bold] {analysis_preview}")
157
+ except MarkupError as me:
158
+ # If markup fails, print a warning and the escaped preview
159
+ rprint(f"[bold yellow]Warning:[/bold yellow] Analysis preview contained invalid markup: {me}")
160
+ rprint(f"[bold]Raw Analysis preview (escaped):[/bold] {escape(analysis_preview)}")
161
+ except Exception as e:
162
+ # Handle other potential errors during preview printing
163
+ rprint(f"[bold red]Error printing analysis preview: {e}[/bold red]")
143
164
  if success:
144
165
  rprint("[bold green]Fixed files saved:[/bold green]")
145
166
  rprint(f" Test file: {output_file_paths['output_test']}")
@@ -263,5 +284,12 @@ def fix_main(
263
284
 
264
285
  except Exception as e:
265
286
  if not ctx.obj.get('quiet', False):
266
- rprint(f"[bold red]Error:[/bold red] {str(e)}")
287
+ # Safely handle and print MarkupError
288
+ if isinstance(e, MarkupError):
289
+ rprint(f"[bold red]Markup Error in fix_main:[/bold red]")
290
+ rprint(escape(str(e)))
291
+ else:
292
+ # Print other errors normally, escaping the error string
293
+ from rich.markup import escape # Ensure escape is imported
294
+ rprint(f"[bold red]Error:[/bold red] {escape(str(e))}")
267
295
  sys.exit(1)
@@ -1,10 +1,22 @@
1
1
  import re
2
- from typing import Dict, Any
2
+ from typing import Dict, Any, Optional
3
3
  from rich import print as rprint
4
4
  from rich.markdown import Markdown
5
+ from pydantic import BaseModel, Field
5
6
  from .load_prompt_template import load_prompt_template
6
7
  from .llm_invoke import llm_invoke
7
8
 
9
+ # Define Pydantic model for structured LLM output for VERIFICATION
10
+ class VerificationOutput(BaseModel):
11
+ issues_count: int = Field(description="The number of issues found during verification.")
12
+ details: Optional[str] = Field(description="Detailed explanation of any discrepancies or issues found. Can be null or empty if issues_count is 0.", default=None)
13
+
14
+ # Define Pydantic model for structured LLM output for FIXES
15
+ class FixerOutput(BaseModel):
16
+ explanation: str = Field(description="Detailed explanation of the analysis and fixes applied.")
17
+ fixed_code: str = Field(description="The complete, runnable, and fixed code module.")
18
+ fixed_program: str = Field(description="The complete, runnable, and fixed program that uses the code module.")
19
+
8
20
  def fix_verification_errors(
9
21
  program: str,
10
22
  prompt: str,
@@ -41,12 +53,13 @@ def fix_verification_errors(
41
53
  verification_issues_count = 0
42
54
  verification_details = None
43
55
  fix_explanation = None
44
- fixed_program = program
45
- fixed_code = code
56
+ fixed_program = program # Initialize with original program
57
+ fixed_code = code # Initialize with original code
46
58
  final_explanation = None
47
59
 
48
- if not all([program, prompt, code, output]):
49
- rprint("[bold red]Error:[/bold red] Missing one or more required inputs (program, prompt, code, output).")
60
+ # Check only essential inputs, allow empty output
61
+ if not all([program, prompt, code]):
62
+ rprint("[bold red]Error:[/bold red] Missing one or more required inputs (program, prompt, code).")
50
63
  return {
51
64
  "explanation": None,
52
65
  "fixed_program": program,
@@ -103,10 +116,10 @@ def fix_verification_errors(
103
116
  strength=strength,
104
117
  temperature=temperature,
105
118
  verbose=False,
119
+ output_pydantic=VerificationOutput
106
120
  )
107
121
  total_cost += verification_response.get('cost', 0.0)
108
122
  model_name = verification_response.get('model_name', model_name)
109
- verification_result = verification_response.get('result', '')
110
123
 
111
124
  if verbose:
112
125
  rprint(f"[cyan]Verification LLM call complete.[/cyan]")
@@ -121,52 +134,90 @@ def fix_verification_errors(
121
134
  "fixed_code": code,
122
135
  "total_cost": total_cost,
123
136
  "model_name": model_name,
124
- "verification_issues_count": verification_issues_count,
137
+ "verification_issues_count": 0, # Reset on LLM call error
125
138
  }
126
139
 
127
- if verbose:
128
- rprint("\n[blue]Verification Result:[/blue]")
129
- rprint(Markdown(verification_result))
130
-
131
140
  issues_found = False
132
- try:
133
- count_match = re.search(r"<issues_count>(\d+)</issues_count>", verification_result)
134
- if count_match:
135
- verification_issues_count = int(count_match.group(1))
136
- else:
137
- rprint("[yellow]Warning:[/yellow] Could not find <issues_count> tag in verification result. Assuming 0 issues.")
138
- verification_issues_count = 0
141
+ verification_result_obj = verification_response.get('result')
142
+
143
+ if isinstance(verification_result_obj, VerificationOutput):
144
+ verification_issues_count = verification_result_obj.issues_count
145
+ verification_details = verification_result_obj.details
146
+ if verbose:
147
+ rprint("[green]Successfully parsed structured output from verification LLM.[/green]")
148
+ rprint("\n[blue]Verification Result (parsed):[/blue]")
149
+ rprint(f" Issues Count: {verification_issues_count}")
150
+ if verification_details:
151
+ rprint(Markdown(f"**Details:**\n{verification_details}"))
152
+ else:
153
+ rprint(" Details: None provided or no issues found.")
139
154
 
140
155
  if verification_issues_count > 0:
141
- details_match = re.search(r"<details>(.*?)</details>", verification_result, re.DOTALL)
142
- if details_match:
143
- verification_details = details_match.group(1).strip()
144
- if verification_details:
145
- issues_found = True
146
- if verbose:
147
- rprint(f"\n[yellow]Found {verification_issues_count} potential issues. Proceeding to fix step.[/yellow]")
148
- else:
149
- rprint("[yellow]Warning:[/yellow] <issues_count> is > 0, but <details> tag is empty. Treating as no issues found.")
150
- verification_issues_count = 0
156
+ if verification_details and verification_details.strip():
157
+ issues_found = True
158
+ if verbose:
159
+ rprint(f"\n[yellow]Found {verification_issues_count} potential issues. Proceeding to fix step.[/yellow]")
151
160
  else:
152
- rprint("[yellow]Warning:[/yellow] <issues_count> is > 0, but could not find <details> tag. Treating as no issues found.")
161
+ rprint(f"[yellow]Warning:[/yellow] <issues_count> is {verification_issues_count}, but <details> field is empty or missing. Treating as no actionable issues found.")
153
162
  verification_issues_count = 0
154
163
  else:
155
164
  if verbose:
156
- rprint("\n[green]No issues found during verification.[/green]")
165
+ rprint("\n[green]No issues found during verification based on structured output.[/green]")
166
+ elif isinstance(verification_result_obj, str):
167
+ try:
168
+ issues_match = re.search(r'<issues_count>(\d+)</issues_count>', verification_result_obj)
169
+ if issues_match:
170
+ parsed_issues_count = int(issues_match.group(1))
171
+ details_match = re.search(r'<details>(.*?)</details>', verification_result_obj, re.DOTALL)
172
+ parsed_verification_details = details_match.group(1).strip() if (details_match and details_match.group(1)) else None
157
173
 
158
- except ValueError:
159
- rprint("[bold red]Error:[/bold red] Could not parse integer value from <issues_count> tag.")
160
- return {
161
- "explanation": None,
162
- "fixed_program": program,
163
- "fixed_code": code,
164
- "total_cost": total_cost,
165
- "model_name": model_name,
166
- "verification_issues_count": 0,
167
- }
168
- except Exception as e:
169
- rprint(f"[bold red]Error parsing verification result:[/bold red] {e}")
174
+
175
+ if parsed_issues_count > 0:
176
+ if parsed_verification_details: # Check if details exist and are not empty
177
+ issues_found = True
178
+ verification_issues_count = parsed_issues_count
179
+ verification_details = parsed_verification_details
180
+ if verbose:
181
+ rprint(f"\n[yellow]Found {verification_issues_count} potential issues in string response. Proceeding to fix step.[/yellow]")
182
+ else:
183
+ rprint(f"[yellow]Warning:[/yellow] <issues_count> is {parsed_issues_count} in string response, but <details> field is empty or missing. Treating as no actionable issues found.")
184
+ verification_issues_count = 0
185
+ issues_found = False
186
+ else: # parsed_issues_count == 0
187
+ verification_issues_count = 0
188
+ issues_found = False
189
+ if verbose:
190
+ rprint("\n[green]No issues found in string verification based on <issues_count> being 0.[/green]")
191
+ else: # issues_match is None (tag not found or content not digits)
192
+ rprint("[bold red]Error:[/bold red] Could not find or parse integer value from <issues_count> tag in string response.")
193
+ return {
194
+ "explanation": None,
195
+ "fixed_program": program,
196
+ "fixed_code": code,
197
+ "total_cost": total_cost,
198
+ "model_name": model_name,
199
+ "verification_issues_count": 0,
200
+ }
201
+ except ValueError: # Should not be hit if regex is \d+, but as a safeguard
202
+ rprint("[bold red]Error:[/bold red] Invalid non-integer value in <issues_count> tag in string response.")
203
+ return {
204
+ "explanation": None,
205
+ "fixed_program": program,
206
+ "fixed_code": code,
207
+ "total_cost": total_cost,
208
+ "model_name": model_name,
209
+ "verification_issues_count": 0,
210
+ }
211
+ else: # Not VerificationOutput and not a successfully parsed string
212
+ rprint(f"[bold red]Error:[/bold red] Verification LLM call did not return the expected structured output (e.g., parsing failed).")
213
+ rprint(f" [dim]Expected type:[/dim] {VerificationOutput} or str")
214
+ rprint(f" [dim]Received type:[/dim] {type(verification_result_obj)}")
215
+ content_str = str(verification_result_obj)
216
+ rprint(f" [dim]Received content:[/dim] {content_str[:500]}{'...' if len(content_str) > 500 else ''}")
217
+ raw_text = verification_response.get('result_text')
218
+ if raw_text:
219
+ raw_text_str = str(raw_text)
220
+ rprint(f" [dim]Raw LLM text (if available from llm_invoke):[/dim] {raw_text_str[:500]}{'...' if len(raw_text_str) > 500 else ''}")
170
221
  return {
171
222
  "explanation": None,
172
223
  "fixed_program": program,
@@ -195,59 +246,79 @@ def fix_verification_errors(
195
246
  strength=strength,
196
247
  temperature=temperature,
197
248
  verbose=False,
249
+ output_pydantic=FixerOutput
198
250
  )
199
251
  total_cost += fix_response.get('cost', 0.0)
200
252
  model_name = fix_response.get('model_name', model_name)
201
- fix_result = fix_response.get('result', '')
202
253
 
203
254
  if verbose:
204
255
  rprint(f"[cyan]Fix LLM call complete.[/cyan]")
205
256
  rprint(f" [dim]Model Used:[/dim] {fix_response.get('model_name', 'N/A')}")
206
257
  rprint(f" [dim]Cost:[/dim] ${fix_response.get('cost', 0.0):.6f}")
207
- rprint("\n[blue]Fix Result:[/blue]")
208
- rprint(Markdown(fix_result))
209
258
 
210
- fixed_program_match = re.search(r"<fixed_program>(.*?)</fixed_program>", fix_result, re.DOTALL)
211
- fixed_code_match = re.search(r"<fixed_code>(.*?)</fixed_code>", fix_result, re.DOTALL)
212
- explanation_match = re.search(r"<explanation>(.*?)</explanation>", fix_result, re.DOTALL)
259
+ fix_result_obj = fix_response.get('result')
260
+ parsed_fix_successfully = False
213
261
 
214
- if fixed_program_match:
215
- fixed_program = fixed_program_match.group(1).strip()
216
- if verbose: rprint("[green]Extracted fixed program.[/green]")
217
- else:
218
- if verbose: rprint("[yellow]Warning:[/yellow] Could not find <fixed_program> tag in fix result. Using original program.")
262
+ if isinstance(fix_result_obj, FixerOutput):
263
+ fixed_program = fix_result_obj.fixed_program
264
+ fixed_code = fix_result_obj.fixed_code
265
+ fix_explanation = fix_result_obj.explanation
266
+ parsed_fix_successfully = True
267
+ if verbose:
268
+ rprint("[green]Successfully parsed structured output for fix.[/green]")
269
+ rprint(Markdown(f"**Explanation from LLM:**\n{fix_explanation}"))
270
+ elif isinstance(fix_result_obj, str):
271
+ program_match = re.search(r'<fixed_program>(.*?)</fixed_program>', fix_result_obj, re.DOTALL)
272
+ code_match = re.search(r'<fixed_code>(.*?)</fixed_code>', fix_result_obj, re.DOTALL)
273
+ explanation_match = re.search(r'<explanation>(.*?)</explanation>', fix_result_obj, re.DOTALL)
219
274
 
220
- if fixed_code_match:
221
- fixed_code = fixed_code_match.group(1).strip()
222
- if verbose: rprint("[green]Extracted fixed code module.[/green]")
223
- else:
224
- if verbose: rprint("[yellow]Warning:[/yellow] Could not find <fixed_code> tag in fix result. Using original code module.")
275
+ if program_match or code_match or explanation_match: # If any tag is found, attempt to parse
276
+ fixed_program_candidate = program_match.group(1).strip() if (program_match and program_match.group(1)) else None
277
+ fixed_code_candidate = code_match.group(1).strip() if (code_match and code_match.group(1)) else None
278
+ fix_explanation_candidate = explanation_match.group(1).strip() if (explanation_match and explanation_match.group(1)) else None
225
279
 
226
- if explanation_match:
227
- fix_explanation = explanation_match.group(1).strip()
228
- if verbose: rprint("[green]Extracted fix explanation.[/green]")
229
- else:
230
- if verbose: rprint("[yellow]Warning:[/yellow] Could not find <explanation> tag in fix result.")
231
- fix_explanation = "[Fix explanation not provided by LLM]"
280
+ fixed_program = fixed_program_candidate if fixed_program_candidate else program
281
+ fixed_code = fixed_code_candidate if fixed_code_candidate else code
282
+ fix_explanation = fix_explanation_candidate if fix_explanation_candidate else "[Fix explanation not provided by LLM]"
283
+ parsed_fix_successfully = True
232
284
 
233
- except Exception as e:
234
- rprint(f"[bold red]Error during fix LLM call or extraction:[/bold red] {e}")
235
- if verification_details and fix_explanation is None:
236
- fix_explanation = f"[Error during fix generation: {e}]"
285
+ if verbose:
286
+ if not program_match or not fixed_program_candidate:
287
+ rprint("[yellow]Warning:[/yellow] Could not find or parse <fixed_program> tag in fix result string. Using original program.")
288
+ if not code_match or not fixed_code_candidate:
289
+ rprint("[yellow]Warning:[/yellow] Could not find or parse <fixed_code> tag in fix result string. Using original code module.")
290
+ if not explanation_match or not fix_explanation_candidate:
291
+ rprint("[yellow]Warning:[/yellow] Could not find or parse <explanation> tag in fix result string. Using default explanation.")
292
+ # else: string, but no relevant tags. Will fall to parsed_fix_successfully = False below
237
293
 
238
- if verbose:
239
- rprint(f"\n[bold blue]Total Cost for fix_verification_errors run:[/bold blue] ${total_cost:.6f}")
294
+ if not parsed_fix_successfully:
295
+ rprint(f"[bold red]Error:[/bold red] Fix generation LLM call did not return the expected structured output (e.g., parsing failed).")
296
+ rprint(f" [dim]Expected type:[/dim] {FixerOutput} or str (with XML tags)")
297
+ rprint(f" [dim]Received type:[/dim] {type(fix_result_obj)}")
298
+ content_str = str(fix_result_obj)
299
+ rprint(f" [dim]Received content:[/dim] {content_str[:500]}{'...' if len(content_str) > 500 else ''}")
300
+ raw_text = fix_response.get('result_text')
301
+ if raw_text:
302
+ raw_text_str = str(raw_text)
303
+ rprint(f" [dim]Raw LLM text (if available from llm_invoke):[/dim] {raw_text_str[:500]}{'...' if len(raw_text_str) > 500 else ''}")
304
+ fix_explanation = "[Error: Failed to parse structured output from LLM for fix explanation]"
305
+ # fixed_program and fixed_code remain original (already initialized)
306
+
307
+ except Exception as e:
308
+ rprint(f"[bold red]Error during fix LLM call or processing structured output:[/bold red] {e}")
309
+ fix_explanation = f"[Error during fix generation: {e}]"
310
+ # fixed_program and fixed_code remain original
240
311
 
241
- if issues_found and verification_details and fix_explanation:
312
+ if issues_found:
242
313
  final_explanation = (
243
314
  f"<verification_details>{verification_details}</verification_details>\n"
244
315
  f"<fix_explanation>{fix_explanation}</fix_explanation>"
245
316
  )
246
- elif issues_found and verification_details:
247
- final_explanation = (
248
- f"<verification_details>{verification_details}</verification_details>\n"
249
- f"<fix_explanation>[Fix explanation not available or extraction failed]</fix_explanation>"
250
- )
317
+ else:
318
+ final_explanation = None # Or "" if an empty list/None is preferred per prompt for "no issues"
319
+
320
+ if verbose:
321
+ rprint(f"\n[bold blue]Total Cost for fix_verification_errors run:[/bold blue] ${total_cost:.6f}")
251
322
 
252
323
  return {
253
324
  "explanation": final_explanation,
@@ -256,4 +327,4 @@ def fix_verification_errors(
256
327
  "total_cost": total_cost,
257
328
  "model_name": model_name,
258
329
  "verification_issues_count": verification_issues_count,
259
- }
330
+ }