pdd-cli 0.0.12__py3-none-any.whl → 0.0.14__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/cli.py CHANGED
@@ -46,7 +46,7 @@ console = Console()
46
46
  @click.option("--review-examples", is_flag=True,
47
47
  help="Review and optionally exclude few-shot examples before command execution.")
48
48
  @click.option('--local', is_flag=True, help='Run commands locally instead of in the cloud.')
49
- @click.version_option(version="0.0.12")
49
+ @click.version_option(version="0.0.14")
50
50
  @click.pass_context
51
51
  def cli(
52
52
  ctx,
pdd/crash_main.py CHANGED
@@ -66,7 +66,7 @@ def crash_main(
66
66
  error_content = input_strings["error_file"]
67
67
 
68
68
  # Get model parameters from context
69
- strength = ctx.obj.get('strength', 0.9)
69
+ strength = ctx.obj.get('strength', 0.97)
70
70
  temperature = ctx.obj.get('temperature', 0)
71
71
 
72
72
  if loop:
@@ -98,9 +98,15 @@ def crash_main(
98
98
  attempts = 1
99
99
 
100
100
  # Determine if contents were actually updated
101
- update_code = final_code != code_content
102
- update_program = final_program != program_content
103
-
101
+ if final_code != "":
102
+ update_code = final_code != code_content
103
+ else:
104
+ update_code = False
105
+ if final_program != "":
106
+ update_program = final_program != program_content
107
+ else:
108
+ update_program = False
109
+
104
110
  # Save results if contents changed
105
111
  if update_code and output_file_paths.get("output"):
106
112
  with open(output_file_paths["output"], 'w') as f:
pdd/fix_error_loop.py CHANGED
@@ -1,11 +1,14 @@
1
1
  #!/usr/bin/env python3
2
2
  import os
3
3
  import sys
4
- import re
5
4
  import subprocess
6
5
  import shutil
7
6
  from datetime import datetime
8
7
 
8
+ # Added for the new pytest-based reporting:
9
+ # import pytest
10
+ # import io
11
+
9
12
  from rich import print as rprint
10
13
  from rich.console import Console
11
14
 
@@ -18,30 +21,59 @@ def escape_brackets(text: str) -> str:
18
21
  """Escape square brackets so Rich doesn't misinterpret them."""
19
22
  return text.replace("[", "\\[").replace("]", "\\]")
20
23
 
21
- def extract_pytest_summary(log_contents: str) -> (int, int, int):
24
+ def run_pytest_on_file(test_file: str) -> (int, int, int, str):
22
25
  """
23
- Extract the number of fails, errors and warnings from pytest output.
24
- Try to match a typical summary line first; if not found, fall back to individual regex searches.
25
- Returns a tuple: (fails, errors, warnings)
26
+ Run pytest on the specified test file using a custom plugin to capture results.
27
+ Returns a tuple: (failures, errors, warnings, logs)
26
28
  """
27
- fails, errors, warnings = sys.maxsize, sys.maxsize, sys.maxsize # defaults if not found
28
- summary_pattern = re.compile(
29
- r"=+\s*(\d+)\s+failed.*?,.*?(\d+)\s+passed.*?,.*?(\d+)\s+warnings", re.IGNORECASE | re.DOTALL
30
- )
31
- match = summary_pattern.search(log_contents)
32
- if match:
33
- fails = int(match.group(1))
34
- # Some pytest outputs lump failures and errors together, but let's keep them the same if not distinct:
35
- errors = int(match.group(1))
36
- warnings = int(match.group(3))
37
- else:
38
- failed_match = re.search(r"(\d+)\s+failed", log_contents, re.IGNORECASE)
39
- errors_match = re.search(r"(\d+)\s+error", log_contents, re.IGNORECASE)
40
- warnings_match = re.search(r"(\d+)\s+warning", log_contents, re.IGNORECASE)
41
- fails = int(failed_match.group(1)) if failed_match else 0
42
- errors = int(errors_match.group(1)) if errors_match else fails
43
- warnings = int(warnings_match.group(1)) if warnings_match else 0
44
- return fails, errors, warnings
29
+ import pytest
30
+ import io
31
+ # import sys
32
+
33
+ class TestResultCollector:
34
+ def __init__(self):
35
+ self.failures = 0
36
+ self.errors = 0
37
+ self.warnings = 0
38
+ self.logs = io.StringIO() # Capture logs in memory
39
+
40
+ def pytest_runtest_logreport(self, report):
41
+ """Capture test failures and errors"""
42
+ if report.when == "call":
43
+ if report.failed:
44
+ self.failures += 1
45
+ elif report.outcome == "error":
46
+ self.errors += 1
47
+ if report.when == "setup" and report.failed:
48
+ self.errors += 1
49
+ if report.when == "teardown" and report.failed:
50
+ self.errors += 1
51
+
52
+ def pytest_sessionfinish(self, session):
53
+ """Capture warnings from pytest session"""
54
+ terminal_reporter = session.config.pluginmanager.get_plugin("terminalreporter")
55
+ if terminal_reporter:
56
+ self.warnings = len(terminal_reporter.stats.get("warnings", []))
57
+
58
+ def capture_logs(self):
59
+ """Redirect stdout and stderr to capture logs"""
60
+ sys.stdout = self.logs
61
+ sys.stderr = self.logs
62
+
63
+ def get_logs(self):
64
+ """Return captured logs and reset stdout/stderr"""
65
+ sys.stdout = sys.__stdout__
66
+ sys.stderr = sys.__stderr__
67
+ return self.logs.getvalue()
68
+
69
+ collector = TestResultCollector()
70
+ collector.capture_logs()
71
+ try:
72
+ # Run pytest on the given test file.
73
+ pytest.main(["-vv", test_file], plugins=[collector])
74
+ finally:
75
+ logs = collector.get_logs()
76
+ return collector.failures, collector.errors, collector.warnings, logs
45
77
 
46
78
  def fix_error_loop(unit_test_file: str,
47
79
  code_file: str,
@@ -55,10 +87,12 @@ def fix_error_loop(unit_test_file: str,
55
87
  verbose: bool = False):
56
88
  """
57
89
  Attempt to fix errors in a unit test and corresponding code using repeated iterations,
58
- counting only the number of times we actually call the LLM fix function. The tests
59
- are re-run in the same iteration after a fix to see if we've succeeded, so that
60
- 'attempts' matches the number of fix attempts (not the total test runs).
61
-
90
+ counting only the number of times we actually call the LLM fix function.
91
+ The tests are re-run in the same iteration after a fix to see if we've succeeded,
92
+ so that 'attempts' matches the number of fix attempts (not the total test runs).
93
+
94
+ This updated version uses pytest's API directly to retrieve failures, errors, and warnings.
95
+
62
96
  Inputs:
63
97
  unit_test_file: Path to the file containing unit tests.
64
98
  code_file: Path to the file containing the code under test.
@@ -124,11 +158,9 @@ def fix_error_loop(unit_test_file: str,
124
158
  with open(error_log_file, "a") as elog:
125
159
  elog.write(f"\n{iteration_header}\n")
126
160
 
127
- # 1) Run the unit tests:
161
+ # 1) Run the unit tests using pytest's API directly.
128
162
  try:
129
- pytest_cmd = [sys.executable, "-m", "pytest", "-vv", "--no-cov", unit_test_file]
130
- result = subprocess.run(pytest_cmd, capture_output=True, text=True)
131
- pytest_output = result.stdout + "\n" + result.stderr
163
+ fails, errors, warnings, pytest_output = run_pytest_on_file(unit_test_file)
132
164
  except Exception as e:
133
165
  rprint(f"[red]Error running pytest:[/red] {e}")
134
166
  return False, "", "", fix_attempts, total_cost, model_name
@@ -139,12 +171,10 @@ def fix_error_loop(unit_test_file: str,
139
171
 
140
172
  # Print to console (escaped):
141
173
  rprint(f"[magenta]Pytest output:[/magenta]\n{escape_brackets(pytest_output)}")
142
-
143
- fails, errors, warnings = extract_pytest_summary(pytest_output)
144
174
  if verbose:
145
175
  rprint(f"[cyan]Iteration summary: {fails} failed, {errors} errors, {warnings} warnings[/cyan]")
146
176
 
147
- # If test is fully successful, we break out:
177
+ # If tests are fully successful, we break out:
148
178
  if fails == 0 and errors == 0 and warnings == 0:
149
179
  rprint("[green]All tests passed with no warnings! Exiting loop.[/green]")
150
180
  break
@@ -196,7 +226,7 @@ def fix_error_loop(unit_test_file: str,
196
226
 
197
227
  # Call fix:
198
228
  try:
199
- # read error log file into pytest_output so it has history of all previous attempts:
229
+ # Read error log file into pytest_output so it has history of all previous attempts:
200
230
  with open(error_log_file, "r") as f:
201
231
  pytest_output = f.read()
202
232
 
@@ -222,10 +252,7 @@ def fix_error_loop(unit_test_file: str,
222
252
  rprint(f"[red]Exceeded the budget of ${budget:.6f}. Ending fixing loop.[/red]")
223
253
  break
224
254
 
225
- # Even if no changes, the tests require we continue up to max_attempts
226
- # so skip the old "break if no changes" logic.
227
-
228
- # If updated_unit_test is True, write to file:
255
+ # Update unit test file if needed.
229
256
  if updated_unit_test:
230
257
  try:
231
258
  with open(unit_test_file, "w") as f:
@@ -236,7 +263,7 @@ def fix_error_loop(unit_test_file: str,
236
263
  rprint(f"[red]Error writing updated unit test:[/red] {e}")
237
264
  break
238
265
 
239
- # If updated_code is True, write it and run verification:
266
+ # Update code file and run verification if needed.
240
267
  if updated_code:
241
268
  try:
242
269
  with open(code_file, "w") as f:
@@ -274,13 +301,10 @@ def fix_error_loop(unit_test_file: str,
274
301
  except Exception as e:
275
302
  rprint(f"[red]Error restoring backup code file:[/red] {e}")
276
303
  break
277
- # We do NOT break or exit this for-loop; let next iteration attempt to fix again.
278
-
279
- # IMPORTANT: Re-run the tests in the *same* iteration to see if we have fixed the problem:
280
- # So that if the new code or new test is good, we can break out with exactly one fix_attempt.
304
+
305
+ # Re-run the tests in the same iteration:
281
306
  try:
282
- second_run_result = subprocess.run(pytest_cmd, capture_output=True, text=True)
283
- second_run_output = second_run_result.stdout + "\n" + second_run_result.stderr
307
+ fails2, errors2, warnings2, second_run_output = run_pytest_on_file(unit_test_file)
284
308
  except Exception as e:
285
309
  rprint(f"[red]Error running second pytest attempt in iteration {iteration}:[/red] {e}")
286
310
  return False, "", "", fix_attempts, total_cost, model_name
@@ -291,12 +315,10 @@ def fix_error_loop(unit_test_file: str,
291
315
 
292
316
  rprint(f"[magenta]Second pytest check:[/magenta]\n{escape_brackets(second_run_output)}")
293
317
 
294
- fails2, errors2, warnings2 = extract_pytest_summary(second_run_output)
295
318
  if fails2 == 0 and errors2 == 0 and warnings2 == 0:
296
319
  rprint("[green]All tests passed on the second run of this iteration! Exiting loop.[/green]")
297
320
  break
298
321
  else:
299
- # Update best iteration if needed:
300
322
  if (errors2 < best_iteration_info["errors"] or
301
323
  (errors2 == best_iteration_info["errors"] and fails2 < best_iteration_info["fails"]) or
302
324
  (errors2 == best_iteration_info["errors"] and fails2 == best_iteration_info["fails"] and warnings2 < best_iteration_info["warnings"])):
@@ -308,16 +330,14 @@ def fix_error_loop(unit_test_file: str,
308
330
  "unit_test_backup": unit_test_backup,
309
331
  "code_backup": code_backup
310
332
  }
311
- # If still not passing, we simply continue to the next iteration in the while loop.
312
333
 
313
- # After we exit the while or exceed attempts/budget, run pytest once more to get final stats:
334
+ # Final test run:
314
335
  try:
315
- final_pytest_cmd = [sys.executable, "-m", "pytest", "-vv", "--no-cov", unit_test_file]
316
- final_result = subprocess.run(final_pytest_cmd, capture_output=True, text=True)
317
- final_output = final_result.stdout + "\n" + final_result.stderr
336
+ final_fails, final_errors, final_warnings, final_output = run_pytest_on_file(unit_test_file)
318
337
  except Exception as e:
319
338
  rprint(f"[red]Error running final pytest:[/red] {e}")
320
339
  final_output = f"Error: {e}"
340
+ final_fails = final_errors = final_warnings = sys.maxsize
321
341
 
322
342
  with open(error_log_file, "a") as elog:
323
343
  elog.write("\n=== Final Pytest Run ===\n")
@@ -325,13 +345,9 @@ def fix_error_loop(unit_test_file: str,
325
345
 
326
346
  rprint(f"[blue]Final pytest output:[/blue]\n{escape_brackets(final_output)}")
327
347
 
328
- # Possibly restore best iteration if the final run is not the best:
329
- # The prompt says: "If the last run isn't the best iteration, restore the best."
330
- final_fails, final_errors, final_warnings = extract_pytest_summary(final_output)
348
+ # Possibly restore best iteration if the final run is not as good:
331
349
  if best_iteration_info["attempt"] is not None:
332
- # Compare final run to best iteration:
333
350
  is_better_final = False
334
- # If final has strictly fewer errors, or tie then fewer fails, or tie then fewer warnings => keep final
335
351
  if final_errors < best_iteration_info["errors"]:
336
352
  is_better_final = True
337
353
  elif final_errors == best_iteration_info["errors"] and final_fails < best_iteration_info["fails"]:
@@ -363,8 +379,6 @@ def fix_error_loop(unit_test_file: str,
363
379
  rprint(f"[red]Error reading final files:[/red] {e}")
364
380
  final_unit_test, final_code = "", ""
365
381
 
366
- # Check final results for success (no fails, no errors, no warnings)
367
- final_fails, final_errors, final_warnings = extract_pytest_summary(final_output)
368
382
  success = (final_fails == 0 and final_errors == 0 and final_warnings == 0)
369
383
  if success:
370
384
  rprint("[green]Final tests passed with no warnings.[/green]")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pdd-cli
3
- Version: 0.0.12
3
+ Version: 0.0.14
4
4
  Summary: PDD (Prompt-Driven Development) Command Line Interface
5
5
  Author: Greg Tanaka
6
6
  Author-email: glt@alumni.caltech.edu
@@ -40,7 +40,7 @@ Requires-Dist: semver==3.0.2
40
40
  Requires-Dist: setuptools==75.1.0
41
41
  Requires-Dist: python-Levenshtein
42
42
 
43
- .. image:: https://img.shields.io/badge/pdd--cli-v0.0.12-blue
43
+ .. image:: https://img.shields.io/badge/pdd--cli-v0.0.14-blue
44
44
  :alt: PDD-CLI Version
45
45
 
46
46
  PDD (Prompt-Driven Development) Command Line Interface
@@ -101,7 +101,7 @@ After installation, verify:
101
101
 
102
102
  pdd --version
103
103
 
104
- You'll see the current PDD version (e.g., 0.0.12).
104
+ You'll see the current PDD version (e.g., 0.0.14).
105
105
 
106
106
  Advanced Installation Tips
107
107
  --------------------------
@@ -6,7 +6,7 @@ pdd/bug_main.py,sha256=myKU9--QWdkV4Wf3mD2PoLPJFNgRjwf4z8s7TC28G_s,3720
6
6
  pdd/bug_to_unit_test.py,sha256=dsJNm6qAwx-m7RvFF5RquFJRzxzZGCWT4IKYnzVCUws,5569
7
7
  pdd/change.py,sha256=iqjWS5DrQ73yMkuUQlwIRIFlofmKdaK6t6-v3zHKL-4,4985
8
8
  pdd/change_main.py,sha256=yL_i1Ws5vt4vAkWiC826csNi2cHP6wKbwe_PfMqbbPY,11407
9
- pdd/cli.py,sha256=Pw-bz_PIuarQNb4hORmgXupwKPGc5hH7xOklAtFatDo,16593
9
+ pdd/cli.py,sha256=nFfCGvuBI6CNYj9GYM8xhOOcgkwcQknX1dQt5Cbsc9Y,16593
10
10
  pdd/cmd_test_main.py,sha256=aSCxRnSurg15AvPcJDAPp9xy8p_qqnjU1oV14Hi2R54,5301
11
11
  pdd/code_generator.py,sha256=n5akrX7VPe71X4RsD6kKqAVvzBLMlciJI4RtJA1PcgA,4375
12
12
  pdd/code_generator_main.py,sha256=G2eRBPXc1cGszkk0PbIPmJZHPaf_dw5d2yZbsvQZA3c,4793
@@ -17,13 +17,13 @@ pdd/construct_paths.py,sha256=8hxkTI_AF5XNpGR4JqCsF4olDBtL8NslXdOZGQt78WM,10039
17
17
  pdd/context_generator.py,sha256=xLquyM6h40Xqg_wcdowqobrLFyZpIvGrOCJD-OBuoy4,5798
18
18
  pdd/context_generator_main.py,sha256=TtsY3jHictdEjmB4cHyNwXmZW_LfHJp3KW3UXyzR2cU,2735
19
19
  pdd/continue_generation.py,sha256=hAVySc6oEsM_Zpj5AWBKEZqMWgoLlQBHcFtkAZ9sZ0E,5192
20
- pdd/crash_main.py,sha256=YngROG62ORLGm-IORLq1vlVVidBGc9g2k0GAmq1jFNM,5287
20
+ pdd/crash_main.py,sha256=ZvOM-450KiTicSpqLIeJf52x6alx8t0Fq3C11LdQiZU,5464
21
21
  pdd/detect_change.py,sha256=ZtgGjGPrD0po-37TEzSbnzFyor7spXHjnT7G6NJ4aCI,5261
22
22
  pdd/detect_change_main.py,sha256=1Z4ymhjJaVr2aliGyqkqeqSmQ7QMgcl23p0wdsmBas0,3653
23
23
  pdd/find_section.py,sha256=lz_FPY4KDCRAGlL1pWVZiutUNv7E4KsDFK-ymDWA_Ec,962
24
24
  pdd/fix_code_loop.py,sha256=L0yxq2yAziPIyFGb8lIP2mvufu8a_gtc5nnN2LuMuKs,8596
25
25
  pdd/fix_code_module_errors.py,sha256=M6AnlR2jF5LI-nNg6gIO5LvSkxiaLIUGyTvfnUfe1cU,4625
26
- pdd/fix_error_loop.py,sha256=Ca8OPag4JHAR4QwaC4ntPgkdkVHtx1HNXynJrZr6tz4,18296
26
+ pdd/fix_error_loop.py,sha256=D6lcWKCYXblpOLASiaj7bWL-Uv6T2u2VyXHObvZuJsw,17520
27
27
  pdd/fix_errors_from_unit_tests.py,sha256=8qCEyHZ6lUSBtV9vhQyhgAxDuhngmOy7vVy2HObckd0,8934
28
28
  pdd/fix_main.py,sha256=02OIViH12BcsykpDp4Osxw2ndEeThnNakMFkzdpYr48,5333
29
29
  pdd/generate_output_paths.py,sha256=zz42GTx9eGyWIYSl3jcWvtJRGnieC3eoPM6DIVcWz2k,7219
@@ -89,9 +89,9 @@ pdd/prompts/trim_results_start_LLM.prompt,sha256=WwFlOHha4wzMLtRHDMI6GtcNdl2toE8
89
89
  pdd/prompts/unfinished_prompt_LLM.prompt,sha256=-JgBpiPTQZdWOAwOG1XpfpD9waynFTAT3Jo84eQ4bTw,1543
90
90
  pdd/prompts/update_prompt_LLM.prompt,sha256=_lGaxeVP4oF8yGqiN6yj6UE0j79lxfGdjsYr5w5KSYk,1261
91
91
  pdd/prompts/xml_convertor_LLM.prompt,sha256=YGRGXJeg6EhM9690f-SKqQrKqSJjLFD51UrPOlO0Frg,2786
92
- pdd_cli-0.0.12.dist-info/LICENSE,sha256=-1bjYH-CEjGEQ8VixtnRYuu37kN6F9NxmZSDkBuUQ9o,1062
93
- pdd_cli-0.0.12.dist-info/METADATA,sha256=HCmK9fJ69EhzkytWSrBX1yeilwuAJPWNveli1foXF38,6808
94
- pdd_cli-0.0.12.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
95
- pdd_cli-0.0.12.dist-info/entry_points.txt,sha256=Kr8HtNVb8uHZtQJNH4DnF8j7WNgWQbb7_Pw5hECSR-I,36
96
- pdd_cli-0.0.12.dist-info/top_level.txt,sha256=xjnhIACeMcMeDfVNREgQZl4EbTni2T11QkL5r7E-sbE,4
97
- pdd_cli-0.0.12.dist-info/RECORD,,
92
+ pdd_cli-0.0.14.dist-info/LICENSE,sha256=-1bjYH-CEjGEQ8VixtnRYuu37kN6F9NxmZSDkBuUQ9o,1062
93
+ pdd_cli-0.0.14.dist-info/METADATA,sha256=GhFCBBWPSLEgXkVJokItqOxM6tZFMYmXTTJNvKAronU,6808
94
+ pdd_cli-0.0.14.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
95
+ pdd_cli-0.0.14.dist-info/entry_points.txt,sha256=Kr8HtNVb8uHZtQJNH4DnF8j7WNgWQbb7_Pw5hECSR-I,36
96
+ pdd_cli-0.0.14.dist-info/top_level.txt,sha256=xjnhIACeMcMeDfVNREgQZl4EbTni2T11QkL5r7E-sbE,4
97
+ pdd_cli-0.0.14.dist-info/RECORD,,