pdd-cli 0.0.41__py3-none-any.whl → 0.0.43__py3-none-any.whl

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

Potentially problematic release.


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

pdd/crash_main.py CHANGED
@@ -52,15 +52,13 @@ def crash_main(
52
52
  ctx.params = ctx.params if isinstance(ctx.params, dict) else {}
53
53
 
54
54
  quiet = ctx.params.get("quiet", ctx.obj.get("quiet", False))
55
- verbose = ctx.params.get("verbose", ctx.obj.get("verbose", False)) # Get verbose flag
55
+ verbose = ctx.params.get("verbose", ctx.obj.get("verbose", False))
56
56
 
57
- # Get model parameters from context early, including time
58
57
  strength = ctx.obj.get("strength", DEFAULT_STRENGTH)
59
58
  temperature = ctx.obj.get("temperature", 0)
60
- time_param = ctx.obj.get("time", DEFAULT_TIME) # Renamed from time_budget for clarity
59
+ time_param = ctx.obj.get("time", DEFAULT_TIME)
61
60
 
62
61
  try:
63
- # Construct file paths
64
62
  input_file_paths = {
65
63
  "prompt_file": prompt_file,
66
64
  "code_file": code_file,
@@ -73,9 +71,8 @@ def crash_main(
73
71
  }
74
72
 
75
73
  force = ctx.params.get("force", ctx.obj.get("force", False))
76
- # quiet = ctx.params.get("quiet", ctx.obj.get("quiet", False)) # Already defined above
77
74
 
78
- input_strings, output_file_paths, _ = construct_paths(
75
+ resolved_config, input_strings, output_file_paths, language = construct_paths(
79
76
  input_file_paths=input_file_paths,
80
77
  force=force,
81
78
  quiet=quiet,
@@ -83,78 +80,79 @@ def crash_main(
83
80
  command_options=command_options
84
81
  )
85
82
 
86
- # Load input files
87
83
  prompt_content = input_strings["prompt_file"]
88
84
  code_content = input_strings["code_file"]
89
85
  program_content = input_strings["program_file"]
90
86
  error_content = input_strings["error_file"]
91
87
 
92
- # Store original content for comparison later
93
88
  original_code_content = code_content
94
89
  original_program_content = program_content
95
90
 
96
- # Get model parameters from context (strength, temperature, time already fetched)
97
- # strength = ctx.obj.get("strength", DEFAULT_STRENGTH) # Moved up
98
- # temperature = ctx.obj.get("temperature", 0) # Moved up
99
- # time_budget = ctx.obj.get("time", DEFAULT_TIME) # Moved up and renamed
100
-
101
- # verbose = ctx.params.get("verbose", ctx.obj.get("verbose", False)) # Already defined above
102
-
103
91
  code_updated: bool = False
104
92
  program_updated: bool = False
105
93
 
106
94
  if loop:
107
- # Use iterative fixing process
108
- # Corrected parameter order for fix_code_loop, adding time_param
109
95
  success, final_program, final_code, attempts, cost, model = fix_code_loop(
110
96
  code_file, prompt_content, program_file, strength, temperature,
111
97
  max_attempts or 3, budget or 5.0, error_file, verbose, time_param
112
98
  )
113
- # Determine if content was updated by fix_code_loop
114
- if success: # Only consider updates if the loop reported success
115
- code_updated = bool(final_code and final_code != original_code_content)
116
- program_updated = bool(final_program and final_program != original_program_content)
99
+ # Always set final_code/final_program to something non-empty
100
+ if not final_code:
101
+ final_code = original_code_content
102
+ if not final_program:
103
+ final_program = original_program_content
104
+ code_updated = final_code != original_code_content
105
+ program_updated = final_program != original_program_content
117
106
  else:
118
- # Use single fix attempt
119
107
  if fix_code_module_errors is None:
120
- raise ImportError("fix_code_module_errors is required but not available.")
121
- # Corrected parameter order for fix_code_module_errors, adding time_param
122
- fm_update_program, fm_update_code, final_program, final_code, program_code_fix, cost, model = fix_code_module_errors(
123
- program_content, prompt_content, code_content, error_content, strength, temperature, verbose, time_param
108
+ raise ImportError("fix_code_module_errors is required but not available.")
109
+
110
+ update_program, update_code, fixed_program, fixed_code, _, cost, model = fix_code_module_errors(
111
+ program_content, prompt_content, code_content, error_content,
112
+ strength, temperature, verbose, time_param
124
113
  )
125
- success = True # Assume success after one attempt if no exception
114
+ success = True
126
115
  attempts = 1
127
- # Use boolean flags from fix_code_module_errors and ensure content is not empty
128
- code_updated = fm_update_code and bool(final_code)
129
- program_updated = fm_update_program and bool(final_program)
130
116
 
131
- # Removed fallback to original content if final_code/final_program are empty
132
- # An empty string from a fix function means no valid update.
117
+ # Fallback if fixed_program is empty but update_program is True
118
+ if update_program and not fixed_program.strip():
119
+ fixed_program = program_content
120
+ if update_code and not fixed_code.strip():
121
+ fixed_code = code_content
122
+
123
+ final_code = fixed_code if update_code else code_content
124
+ final_program = fixed_program if update_program else program_content
125
+
126
+ # Always set final_code/final_program to something non-empty
127
+ if not final_code:
128
+ final_code = original_code_content
129
+ if not final_program:
130
+ final_program = original_program_content
131
+
132
+ code_updated = final_code != original_code_content
133
+ program_updated = final_program != original_program_content
133
134
 
134
- # Determine whether to write the files based on whether paths are provided
135
135
  output_code_path_str = output_file_paths.get("output")
136
136
  output_program_path_str = output_file_paths.get("output_program")
137
137
 
138
- # Write output files if path provided (always write for regression compatibility)
139
- # Use fixed content if available, otherwise use original content
138
+ # Always write output files if output paths are specified
140
139
  if output_code_path_str:
141
140
  output_code_path = Path(output_code_path_str)
142
- output_code_path.parent.mkdir(parents=True, exist_ok=True) # Ensure directory exists
141
+ output_code_path.parent.mkdir(parents=True, exist_ok=True)
143
142
  with open(output_code_path, "w") as f:
144
- f.write(final_code if final_code else original_code_content)
143
+ f.write(final_code)
145
144
 
146
145
  if output_program_path_str:
147
146
  output_program_path = Path(output_program_path_str)
148
- output_program_path.parent.mkdir(parents=True, exist_ok=True) # Ensure directory exists
147
+ output_program_path.parent.mkdir(parents=True, exist_ok=True)
149
148
  with open(output_program_path, "w") as f:
150
- f.write(final_program if final_program else original_program_content)
149
+ f.write(final_program)
151
150
 
152
- # Provide user feedback
153
151
  if not quiet:
154
152
  if success:
155
- rprint("[bold green]Crash fix attempt completed.[/bold green]") # Changed message slightly
153
+ rprint("[bold green]Crash fix attempt completed.[/bold green]")
156
154
  else:
157
- rprint("[bold yellow]Crash fix attempt completed with issues or no changes made.[/bold yellow]") # Changed message
155
+ rprint("[bold yellow]Crash fix attempt completed with issues.[/bold yellow]")
158
156
  rprint(f"[bold]Model used:[/bold] {model}")
159
157
  rprint(f"[bold]Total attempts:[/bold] {attempts}")
160
158
  rprint(f"[bold]Total cost:[/bold] ${cost:.2f}")
@@ -163,25 +161,20 @@ def crash_main(
163
161
  if code_updated:
164
162
  rprint(f"[bold]Fixed code saved to:[/bold] {output_code_path_str}")
165
163
  else:
166
- rprint(f"[info]Code file {Path(code_file).name} was not modified. Original content saved to {output_code_path_str}.[/info]")
167
-
164
+ rprint(f"[info]Code file '{Path(code_file).name}' was not modified (but output file was written).[/info]")
168
165
  if output_program_path_str:
169
166
  if program_updated:
170
167
  rprint(f"[bold]Fixed program saved to:[/bold] {output_program_path_str}")
171
168
  else:
172
- rprint(f"[info]Program file {Path(program_file).name} was not modified. Original content saved to {output_program_path_str}.[/info]")
169
+ rprint(f"[info]Program file '{Path(program_file).name}' was not modified (but output file was written).[/info]")
173
170
 
174
171
  return success, final_code, final_program, attempts, cost, model
175
-
172
+
176
173
  except FileNotFoundError as e:
177
174
  if not quiet:
178
- # Provide a more specific error message for file not found
179
- rprint(f"[bold red]Error:[/bold red] Input file not found: {e}")
175
+ rprint(f"[bold red]Error:[/bold red] Input file not found: {e}")
180
176
  sys.exit(1)
181
177
  except Exception as e:
182
178
  if not quiet:
183
179
  rprint(f"[bold red]An unexpected error occurred:[/bold red] {str(e)}")
184
- # Consider logging the full traceback here for debugging
185
- # import traceback
186
- # traceback.print_exc()
187
180
  sys.exit(1)
pdd/detect_change_main.py CHANGED
@@ -39,7 +39,7 @@ def detect_change_main(
39
39
  "output": output
40
40
  }
41
41
 
42
- input_strings, output_file_paths, _ = construct_paths(
42
+ resolved_config, input_strings, output_file_paths, _ = construct_paths(
43
43
  input_file_paths=input_file_paths,
44
44
  force=ctx.obj.get('force', False),
45
45
  quiet=ctx.obj.get('quiet', False),
@@ -81,6 +81,18 @@ def fix_code_module_errors(
81
81
  model_name = first_response.get('model_name', '')
82
82
  program_code_fix = first_response['result']
83
83
 
84
+ # Check if the LLM response is None or an error string
85
+ if program_code_fix is None:
86
+ error_msg = "LLM returned None result during error analysis"
87
+ if verbose:
88
+ print(f"[red]{error_msg}[/red]")
89
+ raise RuntimeError(error_msg)
90
+ elif isinstance(program_code_fix, str) and program_code_fix.startswith("ERROR:"):
91
+ error_msg = f"LLM failed to analyze errors: {program_code_fix}"
92
+ if verbose:
93
+ print(f"[red]{error_msg}[/red]")
94
+ raise RuntimeError(error_msg)
95
+
84
96
  if verbose:
85
97
  print("[green]Error analysis complete[/green]")
86
98
  print(Markdown(program_code_fix))
pdd/fix_main.py CHANGED
@@ -91,7 +91,7 @@ def fix_main(
91
91
  "output_results": output_results
92
92
  }
93
93
 
94
- input_strings, output_file_paths, _ = construct_paths(
94
+ resolved_config, input_strings, output_file_paths, _ = construct_paths(
95
95
  input_file_paths=input_file_paths,
96
96
  force=ctx.obj.get('force', False),
97
97
  quiet=ctx.obj.get('quiet', False),
@@ -177,7 +177,7 @@ def fix_main(
177
177
  try:
178
178
  # Get JWT token for cloud authentication
179
179
  jwt_token = asyncio.run(get_jwt_token(
180
- firebase_api_key=os.environ.get("REACT_APP_FIREBASE_API_KEY"),
180
+ firebase_api_key=os.environ.get("NEXT_PUBLIC_FIREBASE_API_KEY"),
181
181
  github_client_id=os.environ.get("GITHUB_CLIENT_ID"),
182
182
  app_name="PDD Code Generator"
183
183
  ))
@@ -183,7 +183,7 @@ def fix_verification_main(
183
183
 
184
184
  try:
185
185
  # First try the official helper.
186
- input_strings, output_file_paths, language = construct_paths(
186
+ resolved_config, input_strings, output_file_paths, language = construct_paths(
187
187
  input_file_paths=input_file_paths,
188
188
  force=force,
189
189
  quiet=quiet,
@@ -26,6 +26,7 @@ COMMAND_OUTPUT_KEYS: Dict[str, List[str]] = {
26
26
  'bug': ['output'],
27
27
  'auto-deps': ['output'],
28
28
  'verify': ['output_results', 'output_code', 'output_program'],
29
+ 'sync': ['generate_output_path', 'test_output_path', 'example_output_path'],
29
30
  }
30
31
 
31
32
  # Define default filename patterns for each output key
@@ -48,8 +49,8 @@ DEFAULT_FILENAMES: Dict[str, Dict[str, str]] = {
48
49
  },
49
50
  'change': {'output': 'modified_{basename}.prompt'},
50
51
  'update': {'output': 'modified_{basename}.prompt'}, # Consistent with change/split default
51
- 'detect': {'output': '{basename}_detect.csv'}, # Using basename as change_file_basename isn't available here
52
- 'conflicts': {'output': '{basename}_conflict.csv'}, # Using basename as prompt1/2 basenames aren't available
52
+ 'detect': {'output': '{basename}_detect.csv'}, # basename here is from change_file per construct_paths logic
53
+ 'conflicts': {'output': '{basename}_conflict.csv'}, # basename here is combined sorted prompt basenames per construct_paths logic
53
54
  'crash': {
54
55
  'output': '{basename}_fixed{ext}',
55
56
  # Using basename as program_basename isn't available here
@@ -63,6 +64,11 @@ DEFAULT_FILENAMES: Dict[str, Dict[str, str]] = {
63
64
  'output_code': '{basename}_verified{ext}',
64
65
  'output_program': '{basename}_program_verified{ext}',
65
66
  },
67
+ 'sync': {
68
+ 'generate_output_path': '{basename}{ext}',
69
+ 'test_output_path': 'test_{basename}{ext}',
70
+ 'example_output_path': '{basename}_example{ext}',
71
+ },
66
72
  }
67
73
 
68
74
  # Define the mapping from command/output key to environment variables
@@ -96,6 +102,50 @@ ENV_VAR_MAP: Dict[str, Dict[str, str]] = {
96
102
  'output_code': 'PDD_VERIFY_CODE_OUTPUT_PATH',
97
103
  'output_program': 'PDD_VERIFY_PROGRAM_OUTPUT_PATH',
98
104
  },
105
+ 'sync': {
106
+ 'generate_output_path': 'PDD_GENERATE_OUTPUT_PATH',
107
+ 'test_output_path': 'PDD_TEST_OUTPUT_PATH',
108
+ 'example_output_path': 'PDD_EXAMPLE_OUTPUT_PATH',
109
+ },
110
+ }
111
+
112
+ # Define mapping from context config keys to output keys for different commands
113
+ CONTEXT_CONFIG_MAP: Dict[str, Dict[str, str]] = {
114
+ 'generate': {'output': 'generate_output_path'},
115
+ 'example': {'output': 'example_output_path'},
116
+ 'test': {'output': 'test_output_path'},
117
+ 'sync': {
118
+ 'generate_output_path': 'generate_output_path',
119
+ 'test_output_path': 'test_output_path',
120
+ 'example_output_path': 'example_output_path',
121
+ },
122
+ # For other commands, they can use the general mapping if needed
123
+ 'preprocess': {'output': 'generate_output_path'}, # fallback
124
+ 'fix': {
125
+ 'output_test': 'test_output_path',
126
+ 'output_code': 'generate_output_path',
127
+ 'output_results': 'generate_output_path', # fallback for results
128
+ },
129
+ 'split': {
130
+ 'output_sub': 'generate_output_path', # fallback
131
+ 'output_modified': 'generate_output_path', # fallback
132
+ },
133
+ 'change': {'output': 'generate_output_path'},
134
+ 'update': {'output': 'generate_output_path'},
135
+ 'detect': {'output': 'generate_output_path'},
136
+ 'conflicts': {'output': 'generate_output_path'},
137
+ 'crash': {
138
+ 'output': None, # Use default CWD behavior, not context paths
139
+ 'output_program': None, # Use default CWD behavior, not context paths
140
+ },
141
+ 'trace': {'output': 'generate_output_path'},
142
+ 'bug': {'output': 'test_output_path'},
143
+ 'auto-deps': {'output': 'generate_output_path'},
144
+ 'verify': {
145
+ 'output_results': 'generate_output_path',
146
+ 'output_code': 'generate_output_path',
147
+ 'output_program': 'generate_output_path',
148
+ },
99
149
  }
100
150
 
101
151
  # --- Helper Function ---
@@ -127,14 +177,15 @@ def generate_output_paths(
127
177
  output_locations: Dict[str, Optional[str]],
128
178
  basename: str,
129
179
  language: str,
130
- file_extension: str
180
+ file_extension: str,
181
+ context_config: Optional[Dict[str, str]] = None
131
182
  ) -> Dict[str, str]:
132
183
  """
133
184
  Generates the full, absolute output paths for a given PDD command.
134
185
 
135
- It prioritizes user-specified paths (--output options), then environment
136
- variables, and finally falls back to default naming conventions in the
137
- current working directory.
186
+ It prioritizes user-specified paths (--output options), then context
187
+ configuration from .pddrc, then environment variables, and finally
188
+ falls back to default naming conventions in the current working directory.
138
189
 
139
190
  Args:
140
191
  command: The PDD command being executed (e.g., 'generate', 'fix').
@@ -146,6 +197,8 @@ def generate_output_paths(
146
197
  language: The programming language associated with the operation.
147
198
  file_extension: The file extension (including '.') for the language,
148
199
  used when default patterns require it.
200
+ context_config: Optional dictionary with context-specific paths from .pddrc
201
+ configuration (e.g., {'generate_output_path': 'src/'}).
149
202
 
150
203
  Returns:
151
204
  A dictionary where keys are the standardized output identifiers
@@ -155,8 +208,10 @@ def generate_output_paths(
155
208
  """
156
209
  logger.debug(f"Generating output paths for command: {command}")
157
210
  logger.debug(f"User output locations: {output_locations}")
211
+ logger.debug(f"Context config: {context_config}")
158
212
  logger.debug(f"Basename: {basename}, Language: {language}, Extension: {file_extension}")
159
213
 
214
+ context_config = context_config or {}
160
215
  result_paths: Dict[str, str] = {}
161
216
 
162
217
  if not basename:
@@ -183,6 +238,11 @@ def generate_output_paths(
183
238
  logger.debug(f"Processing output key: {output_key}")
184
239
 
185
240
  user_path: Optional[str] = processed_output_locations.get(output_key)
241
+
242
+ # Get context configuration path for this output key
243
+ context_config_key = CONTEXT_CONFIG_MAP.get(command, {}).get(output_key)
244
+ context_path: Optional[str] = context_config.get(context_config_key) if context_config_key else None
245
+
186
246
  env_var_name: Optional[str] = ENV_VAR_MAP.get(command, {}).get(output_key)
187
247
  env_path: Optional[str] = os.environ.get(env_var_name) if env_var_name else None
188
248
 
@@ -215,7 +275,26 @@ def generate_output_paths(
215
275
  logger.debug(f"User path '{user_path}' identified as a specific file path.")
216
276
  final_path = user_path # Assume it's a full path or filename
217
277
 
218
- # 2. Check Environment Variable Path
278
+ # 2. Check Context Configuration Path (.pddrc)
279
+ elif context_path:
280
+ source = "context"
281
+ # Check if the context path is a directory
282
+ is_dir = context_path.endswith(os.path.sep) or context_path.endswith('/')
283
+ if not is_dir:
284
+ try:
285
+ if os.path.exists(context_path) and os.path.isdir(context_path):
286
+ is_dir = True
287
+ except Exception as e:
288
+ logger.warning(f"Could not check if context path '{context_path}' is a directory: {e}")
289
+
290
+ if is_dir:
291
+ logger.debug(f"Context path '{context_path}' identified as a directory.")
292
+ final_path = os.path.join(context_path, default_filename)
293
+ else:
294
+ logger.debug(f"Context path '{context_path}' identified as a specific file path.")
295
+ final_path = context_path
296
+
297
+ # 3. Check Environment Variable Path
219
298
  elif env_path:
220
299
  source = "environment"
221
300
  # Check if the environment variable points to a directory
@@ -234,7 +313,7 @@ def generate_output_paths(
234
313
  logger.debug(f"Env path '{env_path}' identified as a specific file path.")
235
314
  final_path = env_path # Assume it's a full path or filename
236
315
 
237
- # 3. Use Default Naming Convention in CWD
316
+ # 4. Use Default Naming Convention in CWD
238
317
  else:
239
318
  source = "default"
240
319
  logger.debug(f"Using default filename '{default_filename}' in current directory.")
@@ -273,7 +352,8 @@ if __name__ == '__main__':
273
352
  output_locations={}, # No user input
274
353
  basename=mock_basename,
275
354
  language=mock_language,
276
- file_extension=mock_extension
355
+ file_extension=mock_extension,
356
+ context_config={}
277
357
  )
278
358
  print(f"Result: {paths1}")
279
359
  # Expected: {'output': '/path/to/cwd/my_module.py'}
@@ -285,7 +365,8 @@ if __name__ == '__main__':
285
365
  output_locations={'output': 'generated_code.py'},
286
366
  basename=mock_basename,
287
367
  language=mock_language,
288
- file_extension=mock_extension
368
+ file_extension=mock_extension,
369
+ context_config={}
289
370
  )
290
371
  print(f"Result: {paths2}")
291
372
  # Expected: {'output': '/path/to/cwd/generated_code.py'}
@@ -300,7 +381,8 @@ if __name__ == '__main__':
300
381
  output_locations={'output': test_dir_gen + os.path.sep}, # Explicit directory
301
382
  basename=mock_basename,
302
383
  language=mock_language,
303
- file_extension=mock_extension
384
+ file_extension=mock_extension,
385
+ context_config={}
304
386
  )
305
387
  print(f"Result: {paths3}")
306
388
  # Expected: {'output': '/path/to/cwd/temp_gen_output/my_module.py'}
@@ -319,7 +401,8 @@ if __name__ == '__main__':
319
401
  },
320
402
  basename=mock_basename,
321
403
  language=mock_language,
322
- file_extension=mock_extension
404
+ file_extension=mock_extension,
405
+ context_config={}
323
406
  )
324
407
  print(f"Result: {paths4}")
325
408
  # Expected: {
@@ -344,7 +427,8 @@ if __name__ == '__main__':
344
427
  output_locations={}, # No user input
345
428
  basename=mock_basename,
346
429
  language=mock_language,
347
- file_extension=mock_extension
430
+ file_extension=mock_extension,
431
+ context_config={}
348
432
  )
349
433
  print(f"Result: {paths5}")
350
434
  # Expected: {
@@ -365,7 +449,8 @@ if __name__ == '__main__':
365
449
  output_locations={},
366
450
  basename=mock_basename,
367
451
  language=mock_language,
368
- file_extension=mock_extension # This extension is ignored for preprocess default
452
+ file_extension=mock_extension, # This extension is ignored for preprocess default
453
+ context_config={}
369
454
  )
370
455
  print(f"Result: {paths6}")
371
456
  # Expected: {'output': '/path/to/cwd/my_module_python_preprocessed.prompt'}
@@ -377,7 +462,8 @@ if __name__ == '__main__':
377
462
  output_locations={},
378
463
  basename=mock_basename,
379
464
  language=mock_language,
380
- file_extension=mock_extension
465
+ file_extension=mock_extension,
466
+ context_config={}
381
467
  )
382
468
  print(f"Result: {paths7}")
383
469
  # Expected: {}
@@ -389,7 +475,8 @@ if __name__ == '__main__':
389
475
  output_locations={},
390
476
  basename="complex_prompt",
391
477
  language="javascript",
392
- file_extension=".js" # Ignored for split defaults
478
+ file_extension=".js", # Ignored for split defaults
479
+ context_config={}
393
480
  )
394
481
  print(f"Result: {paths8}")
395
482
  # Expected: {
@@ -404,7 +491,8 @@ if __name__ == '__main__':
404
491
  output_locations={},
405
492
  basename="feature_analysis", # Used instead of change_file_basename
406
493
  language="", # Not relevant for detect default
407
- file_extension="" # Not relevant for detect default
494
+ file_extension="", # Not relevant for detect default
495
+ context_config={}
408
496
  )
409
497
  print(f"Result: {paths9}")
410
498
  # Expected: {'output': '/path/to/cwd/feature_analysis_detect.csv'}
@@ -416,7 +504,8 @@ if __name__ == '__main__':
416
504
  output_locations={},
417
505
  basename="crashed_module", # Used for both code and program defaults
418
506
  language="java",
419
- file_extension=".java"
507
+ file_extension=".java",
508
+ context_config={}
420
509
  )
421
510
  print(f"Result: {paths10}")
422
511
  # Expected: {
@@ -431,7 +520,8 @@ if __name__ == '__main__':
431
520
  output_locations={},
432
521
  basename="module_to_verify",
433
522
  language="python",
434
- file_extension=".py"
523
+ file_extension=".py",
524
+ context_config={}
435
525
  )
436
526
  print(f"Result: {paths11}")
437
527
  # Expected: {
@@ -449,7 +539,8 @@ if __name__ == '__main__':
449
539
  output_locations={'output_program': test_dir_verify_prog + os.path.sep},
450
540
  basename="module_to_verify",
451
541
  language="python",
452
- file_extension=".py"
542
+ file_extension=".py",
543
+ context_config={}
453
544
  )
454
545
  print(f"Result: {paths12}")
455
546
  # Expected: {
@@ -468,7 +559,8 @@ if __name__ == '__main__':
468
559
  output_locations={},
469
560
  basename="another_module_verify",
470
561
  language="python",
471
- file_extension=".py"
562
+ file_extension=".py",
563
+ context_config={}
472
564
  )
473
565
  print(f"Result: {paths13}")
474
566
  # Expected: {
pdd/generate_test.py CHANGED
@@ -72,19 +72,34 @@ def generate_test(
72
72
  model_name = response['model_name']
73
73
  result = response['result']
74
74
 
75
+ # Validate that we got a non-empty result
76
+ if not result or not result.strip():
77
+ raise ValueError(f"LLM test generation returned empty result. Model: {model_name}, Cost: ${response['cost']:.6f}")
78
+
75
79
  if verbose:
76
80
  console.print(Markdown(result))
77
81
  console.print(f"[bold green]Initial generation cost: ${total_cost:.6f}[/bold green]")
78
82
 
79
83
  # Step 4: Check if generation is complete
80
84
  last_600_chars = result[-600:] if len(result) > 600 else result
81
- reasoning, is_finished, check_cost, check_model = unfinished_prompt(
82
- prompt_text=last_600_chars,
83
- strength=strength,
84
- temperature=temperature,
85
- time=time,
86
- verbose=verbose
87
- )
85
+
86
+ # Validate that the last_600_chars is not empty after stripping
87
+ if not last_600_chars.strip():
88
+ # If the tail is empty, assume generation is complete
89
+ if verbose:
90
+ console.print("[bold yellow]Last 600 chars are empty, assuming generation is complete[/bold yellow]")
91
+ reasoning = "Generation appears complete (tail is empty)"
92
+ is_finished = True
93
+ check_cost = 0.0
94
+ check_model = model_name
95
+ else:
96
+ reasoning, is_finished, check_cost, check_model = unfinished_prompt(
97
+ prompt_text=last_600_chars,
98
+ strength=strength,
99
+ temperature=temperature,
100
+ time=time,
101
+ verbose=verbose
102
+ )
88
103
  total_cost += check_cost
89
104
 
90
105
  if not is_finished:
@@ -104,15 +119,37 @@ def generate_test(
104
119
  model_name = continue_model
105
120
 
106
121
  # Process the final result
107
- processed_result, post_cost, post_model = postprocess(
108
- result,
109
- language=language,
110
- strength=EXTRACTION_STRENGTH,
111
- temperature=temperature,
112
- time=time,
113
- verbose=verbose
114
- )
115
- total_cost += post_cost
122
+ try:
123
+ processed_result, post_cost, post_model = postprocess(
124
+ result,
125
+ language=language,
126
+ strength=EXTRACTION_STRENGTH,
127
+ temperature=temperature,
128
+ time=time,
129
+ verbose=verbose
130
+ )
131
+ total_cost += post_cost
132
+ except Exception as e:
133
+ console.print(f"[bold red]Postprocess failed: {str(e)}[/bold red]")
134
+ console.print(f"[bold yellow]Falling back to raw result[/bold yellow]")
135
+
136
+ # Try to extract code blocks directly from the raw result
137
+ import re
138
+ code_blocks = re.findall(r'```(?:python)?\s*(.*?)```', result, re.DOTALL | re.IGNORECASE)
139
+
140
+ if code_blocks:
141
+ # Use the first substantial code block
142
+ for block in code_blocks:
143
+ if len(block.strip()) > 100 and ('def test_' in block or 'import' in block):
144
+ processed_result = block.strip()
145
+ break
146
+ else:
147
+ processed_result = code_blocks[0].strip() if code_blocks else result
148
+ else:
149
+ # No code blocks found, use raw result
150
+ processed_result = result
151
+
152
+ post_cost = 0.0
116
153
 
117
154
  # Step 5: Print total cost if verbose
118
155
  if verbose: