pdd-cli 0.0.47__py3-none-any.whl → 0.0.49__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/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """PDD - Prompt Driven Development"""
2
2
 
3
- __version__ = "0.0.47"
3
+ __version__ = "0.0.49"
4
4
 
5
5
  # Strength parameter used for LLM extraction across the codebase
6
6
  # Used in postprocessing, XML tagging, code generation, and other extraction
pdd/cmd_test_main.py CHANGED
@@ -147,8 +147,20 @@ def cmd_test_main(
147
147
  ctx.exit(1)
148
148
  return "", 0.0, ""
149
149
 
150
- # Handle output - prioritize orchestration output path over construct_paths result
151
- output_file = output or output_file_paths["output"]
150
+ # Handle output - if output is a directory, use resolved file path from construct_paths
151
+ resolved_output = output_file_paths["output"]
152
+ if output is None:
153
+ output_file = resolved_output
154
+ else:
155
+ try:
156
+ is_dir_hint = output.endswith('/')
157
+ except Exception:
158
+ is_dir_hint = False
159
+ # Prefer resolved file if user passed a directory path
160
+ if is_dir_hint or (Path(output).exists() and Path(output).is_dir()):
161
+ output_file = resolved_output
162
+ else:
163
+ output_file = output
152
164
  if merge and existing_tests:
153
165
  output_file = existing_tests
154
166
 
@@ -165,8 +165,19 @@ def code_generator_main(
165
165
  command_options=command_options,
166
166
  )
167
167
  prompt_content = input_strings["prompt_file"]
168
- # Prioritize orchestration output path over construct_paths result
169
- output_path = output or output_file_paths.get("output")
168
+ # Determine final output path: if user passed a directory, use resolved file path
169
+ resolved_output = output_file_paths.get("output")
170
+ if output is None:
171
+ output_path = resolved_output
172
+ else:
173
+ try:
174
+ is_dir_hint = output.endswith(os.path.sep) or output.endswith("/")
175
+ except Exception:
176
+ is_dir_hint = False
177
+ if is_dir_hint or os.path.isdir(output):
178
+ output_path = resolved_output
179
+ else:
180
+ output_path = output
170
181
 
171
182
  except FileNotFoundError as e:
172
183
  console.print(f"[red]Error: Input file not found: {e.filename}[/red]")
@@ -442,4 +453,4 @@ def code_generator_main(
442
453
  if verbose: console.print(traceback.format_exc())
443
454
  return "", was_incremental_operation, total_cost, "error"
444
455
 
445
- return generated_code_content or "", was_incremental_operation, total_cost, model_name
456
+ return generated_code_content or "", was_incremental_operation, total_cost, model_name
pdd/construct_paths.py CHANGED
@@ -181,47 +181,29 @@ def _candidate_prompt_path(input_files: Dict[str, Path]) -> Path | None:
181
181
 
182
182
  # New helper function to check if a language is known
183
183
  def _is_known_language(language_name: str) -> bool:
184
- """Checks if a language name is present in the language_format.csv."""
184
+ """Return True if the language is recognized.
185
+
186
+ Prefer CSV in PDD_PATH if available; otherwise fall back to a built-in set
187
+ so basename/language inference does not fail when PDD_PATH is unset.
188
+ """
189
+ language_name_lower = (language_name or "").lower()
190
+ if not language_name_lower:
191
+ return False
192
+
193
+ builtin_languages = {
194
+ 'python', 'javascript', 'typescript', 'java', 'cpp', 'c', 'go', 'ruby', 'rust',
195
+ 'kotlin', 'swift', 'csharp', 'php', 'scala', 'r', 'lua', 'perl', 'bash', 'shell',
196
+ 'powershell', 'sql', 'prompt', 'html', 'css', 'makefile'
197
+ }
198
+
185
199
  pdd_path_str = os.getenv('PDD_PATH')
186
200
  if not pdd_path_str:
187
- # Consistent with get_extension, raise ValueError if PDD_PATH is not set.
188
- # Or, for an internal helper, we might decide to log and return False,
189
- # but raising an error for missing config is generally safer.
190
- # However, _determine_language (the caller) already raises ValueError
191
- # if language cannot be found, so this path might not be strictly necessary
192
- # if we assume PDD_PATH is validated earlier or by other get_extension/get_language calls.
193
- # For robustness here, let's keep a check but perhaps make it less severe if called internally.
194
- # For now, align with how get_extension might handle it.
195
- # console.print("[error]PDD_PATH environment variable is not set. Cannot validate language.", style="error")
196
- # return False # Or raise error
197
- # Given this is internal and other functions (get_extension) already depend on PDD_PATH,
198
- # we can assume if those ran, PDD_PATH is set. If not, they'd fail first.
199
- # So, we can simplify or rely on that pre-condition.
200
- # Let's assume PDD_PATH will be set if other language functions are working.
201
- # If it's critical, an explicit check and raise ValueError is better.
202
- # For now, let's proceed assuming PDD_PATH is available if this point is reached.
203
- pass # Assuming PDD_PATH is checked by get_extension/get_language if they are called
204
-
205
- # If PDD_PATH is not set, this will likely fail earlier if get_extension/get_language are used.
206
- # If we want this helper to be fully independent, it needs robust PDD_PATH handling.
207
- # Let's assume for now, PDD_PATH is available if this point is reached through normal flow.
208
-
209
- # Re-evaluate: PDD_PATH is critical for this function. Let's keep the check.
210
- if not pdd_path_str:
211
- # This helper might be called before get_extension in some logic paths
212
- # if _determine_language prioritizes suffix checking first.
213
- # So, it needs its own PDD_PATH check.
214
- # Raise ValueError to be consistent with get_extension's behavior.
215
- raise ValueError("PDD_PATH environment variable is not set. Cannot validate language.")
201
+ return language_name_lower in builtin_languages
216
202
 
217
203
  csv_file_path = Path(pdd_path_str) / 'data' / 'language_format.csv'
218
-
219
204
  if not csv_file_path.is_file():
220
- # Raise FileNotFoundError if CSV is missing, consistent with get_extension
221
- raise FileNotFoundError(f"The language format CSV file does not exist: {csv_file_path}")
222
-
223
- language_name_lower = language_name.lower()
224
-
205
+ return language_name_lower in builtin_languages
206
+
225
207
  try:
226
208
  with open(csv_file_path, mode='r', encoding='utf-8', newline='') as csvfile:
227
209
  reader = csv.DictReader(csvfile)
@@ -229,10 +211,10 @@ def _is_known_language(language_name: str) -> bool:
229
211
  if row.get('language', '').lower() == language_name_lower:
230
212
  return True
231
213
  except csv.Error as e:
232
- # Log and return False or raise a custom error
233
214
  console.print(f"[error]CSV Error reading {csv_file_path}: {e}", style="error")
234
- return False # Indicates language could not be confirmed due to CSV issue
235
- return False
215
+ return language_name_lower in builtin_languages
216
+
217
+ return language_name_lower in builtin_languages
236
218
 
237
219
 
238
220
  def _strip_language_suffix(path_like: os.PathLike[str]) -> str:
@@ -354,7 +336,7 @@ def _determine_language(
354
336
 
355
337
  # 4 - Special handling for detect command - default to prompt for LLM prompts
356
338
  if command == "detect" and "change_file" in input_file_paths:
357
- return "prompt" # Default to prompt for detect command
339
+ return "prompt"
358
340
 
359
341
  # 5 - If no language determined, raise error
360
342
  raise ValueError("Could not determine language from input files or options.")
@@ -607,7 +589,23 @@ def construct_paths(
607
589
  style="warning"
608
590
  )
609
591
 
610
- file_extension = get_extension(language) # Pass determined language
592
+
593
+ # Try to get extension from CSV; fallback to built-in mapping if PDD_PATH/CSV unavailable
594
+ try:
595
+ file_extension = get_extension(language) # Pass determined language
596
+ if not file_extension and (language or '').lower() != 'prompt':
597
+ raise ValueError('empty extension')
598
+ except Exception:
599
+ builtin_ext_map = {
600
+ 'python': '.py', 'javascript': '.js', 'typescript': '.ts', 'java': '.java',
601
+ 'cpp': '.cpp', 'c': '.c', 'go': '.go', 'ruby': '.rb', 'rust': '.rs',
602
+ 'kotlin': '.kt', 'swift': '.swift', 'csharp': '.cs', 'php': '.php',
603
+ 'scala': '.scala', 'r': '.r', 'lua': '.lua', 'perl': '.pl', 'bash': '.sh',
604
+ 'shell': '.sh', 'powershell': '.ps1', 'sql': '.sql', 'html': '.html', 'css': '.css',
605
+ 'prompt': '.prompt', 'makefile': ''
606
+ }
607
+ file_extension = builtin_ext_map.get(language.lower(), f".{language.lower()}" if language else '')
608
+
611
609
 
612
610
 
613
611
  # ------------- Step 3b: build output paths ---------------
@@ -697,4 +695,4 @@ def construct_paths(
697
695
  resolved_config["examples_dir"] = str(Path(resolved_config.get("example_output_path", "examples")).parent)
698
696
 
699
697
 
700
- return resolved_config, input_strings, output_file_paths_str_return, language
698
+ return resolved_config, input_strings, output_file_paths_str_return, language
@@ -1,5 +1,6 @@
1
1
  import sys
2
2
  from typing import Tuple, Optional
3
+ from pathlib import Path
3
4
  import click
4
5
  from rich import print as rprint
5
6
 
@@ -51,11 +52,19 @@ def context_generator_main(ctx: click.Context, prompt_file: str, code_file: str,
51
52
  verbose=ctx.obj.get('verbose', False)
52
53
  )
53
54
 
54
- # Save results - prioritize orchestration output path over construct_paths result
55
- final_output_path = output or output_file_paths["output"]
56
- print(f"DEBUG: output param = {output}")
57
- print(f"DEBUG: output_file_paths['output'] = {output_file_paths['output']}")
58
- print(f"DEBUG: final_output_path = {final_output_path}")
55
+ # Save results - if output is a directory, use resolved file path from construct_paths
56
+ resolved_output = output_file_paths["output"]
57
+ if output is None:
58
+ final_output_path = resolved_output
59
+ else:
60
+ try:
61
+ is_dir_hint = output.endswith('/')
62
+ except Exception:
63
+ is_dir_hint = False
64
+ if is_dir_hint or (Path(output).exists() and Path(output).is_dir()):
65
+ final_output_path = resolved_output
66
+ else:
67
+ final_output_path = output
59
68
  if final_output_path and example_code is not None:
60
69
  with open(final_output_path, 'w') as f:
61
70
  f.write(example_code)
@@ -88,4 +97,4 @@ def context_generator_main(ctx: click.Context, prompt_file: str, code_file: str,
88
97
  except Exception as e:
89
98
  if not ctx.obj.get('quiet', False):
90
99
  rprint(f"[bold red]Error:[/bold red] {str(e)}")
91
- sys.exit(1)
100
+ sys.exit(1)
pdd/fix_error_loop.py CHANGED
@@ -281,7 +281,15 @@ def fix_error_loop(unit_test_file: str,
281
281
  elog.write(format_log_for_output(log_structure))
282
282
 
283
283
  # Set success to True (already determined)
284
- # No need to read the files - keep empty strings for passing cases
284
+ # Read the actual fixed files to return the successful state
285
+ try:
286
+ with open(unit_test_file, "r") as f:
287
+ final_unit_test = f.read()
288
+ with open(code_file, "r") as f:
289
+ final_code = f.read()
290
+ except Exception as e:
291
+ rprint(f"[yellow]Warning: Could not read fixed files: {e}[/yellow]")
292
+ # Keep empty strings as fallback
285
293
  break
286
294
 
287
295
  iteration_header = f"=== Attempt iteration {iteration} ==="
pdd/llm_invoke.py CHANGED
@@ -6,6 +6,7 @@ import pandas as pd
6
6
  import litellm
7
7
  import logging # ADDED FOR DETAILED LOGGING
8
8
  import importlib.resources
9
+ from litellm.caching.caching import Cache # Fix for LiteLLM v1.75.5+
9
10
 
10
11
  # --- Configure Standard Python Logging ---
11
12
  logger = logging.getLogger("pdd.llm_invoke")
@@ -234,6 +235,7 @@ if GCS_HMAC_SECRET_ACCESS_KEY:
234
235
  GCS_HMAC_SECRET_ACCESS_KEY = GCS_HMAC_SECRET_ACCESS_KEY.strip()
235
236
 
236
237
  cache_configured = False
238
+ configured_cache = None # Store the configured cache instance for restoration
237
239
 
238
240
  if GCS_BUCKET_NAME and GCS_HMAC_ACCESS_KEY_ID and GCS_HMAC_SECRET_ACCESS_KEY:
239
241
  # Store original AWS credentials before overwriting for GCS cache setup
@@ -247,12 +249,13 @@ if GCS_BUCKET_NAME and GCS_HMAC_ACCESS_KEY_ID and GCS_HMAC_SECRET_ACCESS_KEY:
247
249
  os.environ['AWS_SECRET_ACCESS_KEY'] = GCS_HMAC_SECRET_ACCESS_KEY
248
250
  # os.environ['AWS_REGION_NAME'] = GCS_REGION_NAME # Uncomment if needed
249
251
 
250
- litellm.cache = litellm.Cache(
252
+ configured_cache = Cache(
251
253
  type="s3",
252
254
  s3_bucket_name=GCS_BUCKET_NAME,
253
255
  s3_region_name=GCS_REGION_NAME, # Pass region explicitly to cache
254
256
  s3_endpoint_url=GCS_ENDPOINT_URL,
255
257
  )
258
+ litellm.cache = configured_cache
256
259
  logger.info(f"LiteLLM cache configured for GCS bucket (S3 compatible): {GCS_BUCKET_NAME}")
257
260
  cache_configured = True
258
261
 
@@ -277,15 +280,22 @@ if GCS_BUCKET_NAME and GCS_HMAC_ACCESS_KEY_ID and GCS_HMAC_SECRET_ACCESS_KEY:
277
280
  elif 'AWS_REGION_NAME' in os.environ:
278
281
  pass # Or just leave it if the temporary setting wasn't done/needed
279
282
 
283
+ # Check if caching is disabled via environment variable
284
+ if os.getenv("LITELLM_CACHE_DISABLE") == "1":
285
+ logger.info("LiteLLM caching disabled via LITELLM_CACHE_DISABLE=1")
286
+ litellm.cache = None
287
+ cache_configured = True
288
+
280
289
  if not cache_configured:
281
290
  try:
282
- # Try SQLite-based cache as a fallback
291
+ # Try disk-based cache as a fallback
283
292
  sqlite_cache_path = PROJECT_ROOT / "litellm_cache.sqlite"
284
- litellm.cache = litellm.Cache(type="sqlite", cache_path=str(sqlite_cache_path))
285
- logger.info(f"LiteLLM SQLite cache configured at {sqlite_cache_path}")
293
+ configured_cache = Cache(type="disk", disk_cache_dir=str(sqlite_cache_path))
294
+ litellm.cache = configured_cache
295
+ logger.info(f"LiteLLM disk cache configured at {sqlite_cache_path}")
286
296
  cache_configured = True
287
297
  except Exception as e2:
288
- warnings.warn(f"Failed to configure LiteLLM SQLite cache: {e2}. Caching is disabled.")
298
+ warnings.warn(f"Failed to configure LiteLLM disk cache: {e2}. Caching is disabled.")
289
299
  litellm.cache = None
290
300
 
291
301
  if not cache_configured:
@@ -848,6 +858,10 @@ def llm_invoke(
848
858
  # --- 3. Iterate Through Candidates and Invoke LLM ---
849
859
  last_exception = None
850
860
  newly_acquired_keys: Dict[str, bool] = {} # Track keys obtained in this run
861
+
862
+ # Initialize variables for retry section
863
+ response_format = None
864
+ time_kwargs = {}
851
865
 
852
866
  for model_info in candidate_models:
853
867
  model_name_litellm = model_info['model']
@@ -960,7 +974,8 @@ def llm_invoke(
960
974
  logger.info(f"[INFO] Requesting structured output (Pydantic: {output_pydantic.__name__}) for {model_name_litellm}")
961
975
  # Pass the Pydantic model directly if supported, else use json_object
962
976
  # LiteLLM handles passing Pydantic models for supported providers
963
- litellm_kwargs["response_format"] = output_pydantic
977
+ response_format = output_pydantic
978
+ litellm_kwargs["response_format"] = response_format
964
979
  # As a fallback, one could use:
965
980
  # litellm_kwargs["response_format"] = {"type": "json_object"}
966
981
  # And potentially enable client-side validation:
@@ -982,7 +997,9 @@ def llm_invoke(
982
997
  # Currently known: Anthropic uses 'thinking'
983
998
  # Model name comparison is more robust than provider string
984
999
  if provider == 'anthropic': # Check provider column instead of model prefix
985
- litellm_kwargs["thinking"] = {"type": "enabled", "budget_tokens": budget}
1000
+ thinking_param = {"type": "enabled", "budget_tokens": budget}
1001
+ litellm_kwargs["thinking"] = thinking_param
1002
+ time_kwargs["thinking"] = thinking_param
986
1003
  if verbose:
987
1004
  logger.info(f"[INFO] Requesting Anthropic thinking (budget type) with budget: {budget} tokens for {model_name_litellm}")
988
1005
  else:
@@ -1002,6 +1019,7 @@ def llm_invoke(
1002
1019
  effort = "medium"
1003
1020
  # Use the common 'reasoning_effort' param LiteLLM provides
1004
1021
  litellm_kwargs["reasoning_effort"] = effort
1022
+ time_kwargs["reasoning_effort"] = effort
1005
1023
  if verbose:
1006
1024
  logger.info(f"[INFO] Requesting reasoning_effort='{effort}' (effort type) for {model_name_litellm} based on time={time}")
1007
1025
 
@@ -1104,8 +1122,8 @@ def llm_invoke(
1104
1122
  max_completion_tokens=max_tokens,
1105
1123
  **time_kwargs
1106
1124
  )
1107
- # Re-enable cache
1108
- litellm.cache = Cache()
1125
+ # Re-enable cache - restore original configured cache (restore to original state, even if None)
1126
+ litellm.cache = configured_cache
1109
1127
  # Extract result from retry
1110
1128
  retry_raw_result = retry_response.choices[0].message.content
1111
1129
  if retry_raw_result is not None:
pdd/sync_orchestration.py CHANGED
@@ -17,6 +17,9 @@ from dataclasses import asdict
17
17
 
18
18
  import click
19
19
 
20
+ # --- Constants ---
21
+ MAX_CONSECUTIVE_TESTS = 3 # Allow up to 3 consecutive test attempts
22
+
20
23
  # --- Real PDD Component Imports ---
21
24
  from .sync_animation import sync_animation
22
25
  from .sync_determine_operation import (
@@ -533,6 +536,27 @@ def sync_orchestration(
533
536
  })
534
537
  break
535
538
 
539
+ # Detect consecutive test operations (infinite test loop protection)
540
+ if operation == 'test':
541
+ # Count consecutive test operations
542
+ consecutive_tests = 0
543
+ for i in range(len(operation_history) - 1, -1, -1):
544
+ if operation_history[i] == 'test':
545
+ consecutive_tests += 1
546
+ else:
547
+ break
548
+
549
+ # Use module-level constant for max consecutive test attempts
550
+ if consecutive_tests >= MAX_CONSECUTIVE_TESTS:
551
+ errors.append(f"Detected {consecutive_tests} consecutive test operations. Breaking infinite test loop.")
552
+ errors.append("Coverage target may not be achievable with additional test generation.")
553
+ log_sync_event(basename, language, "cycle_detected", {
554
+ "cycle_type": "consecutive-test",
555
+ "consecutive_count": consecutive_tests,
556
+ "operation_history": operation_history[-10:] # Last 10 operations
557
+ })
558
+ break
559
+
536
560
  if operation in ['all_synced', 'nothing', 'fail_and_request_manual_merge', 'error', 'analyze_conflict']:
537
561
  current_function_name_ref[0] = "synced" if operation in ['all_synced', 'nothing'] else "conflict"
538
562
 
@@ -1024,10 +1048,14 @@ def sync_orchestration(
1024
1048
  success = result.get('success', False)
1025
1049
  current_cost_ref[0] += result.get('cost', 0.0)
1026
1050
  elif isinstance(result, tuple) and len(result) >= 3:
1027
- # Tuple return (e.g., from code_generator_main, context_generator_main)
1028
- # For tuples, success is determined by no exceptions and valid return content
1029
- # Check if the first element (generated content) is None, which indicates failure
1030
- success = result[0] is not None
1051
+ # Tuple return (e.g., from code_generator_main, context_generator_main, cmd_test_main)
1052
+ # For test operations, use file existence as success criteria to match local detection
1053
+ if operation == 'test':
1054
+ success = pdd_files['test'].exists()
1055
+ else:
1056
+ # For other operations, success is determined by valid return content
1057
+ # Check if the first element (generated content) is None, which indicates failure
1058
+ success = result[0] is not None
1031
1059
  # Extract cost from tuple (usually second-to-last element)
1032
1060
  cost = result[-2] if len(result) >= 2 and isinstance(result[-2], (int, float)) else 0.0
1033
1061
  current_cost_ref[0] += cost
@@ -1132,17 +1160,50 @@ def sync_orchestration(
1132
1160
  # Don't fail the entire operation if example re-execution fails
1133
1161
  print(f"Warning: Post-crash example re-execution failed: {e}")
1134
1162
 
1135
- # After successful fix operation, execute tests to update run report
1163
+ # After fix operation, check if fix was successful before re-testing
1136
1164
  if operation == 'fix':
1137
- try:
1138
- test_file = pdd_files['test']
1139
- if test_file.exists():
1140
- _execute_tests_and_create_run_report(
1141
- test_file, basename, language, target_coverage
1165
+ # Extract fix success status from result
1166
+ fix_successful = False
1167
+ if isinstance(result, tuple) and len(result) >= 6:
1168
+ # fix_main returns: (success, fixed_unit_test, fixed_code, attempts, total_cost, model_name)
1169
+ fix_successful = result[0] # First element is success boolean
1170
+ elif isinstance(result, dict):
1171
+ fix_successful = result.get('success', False)
1172
+
1173
+ if fix_successful:
1174
+ # If fix was successful, do NOT re-run tests automatically
1175
+ # The fix already validated that tests pass, so trust that result
1176
+ print(f"Fix operation successful for {basename}. Skipping test re-execution to preserve fix state.")
1177
+
1178
+ # Update run report to indicate tests are now passing
1179
+ # Create a successful run report without actually re-running tests
1180
+ try:
1181
+ run_report = RunReport(
1182
+ timestamp=datetime.datetime.now(datetime.timezone.utc),
1183
+ total_tests=1, # Assume at least 1 test exists since we just fixed it
1184
+ tests_passed=1, # Fix succeeded, so tests are now passing
1185
+ tests_failed=0, # No failures after successful fix
1186
+ coverage=target_coverage, # Use target coverage as achieved
1187
+ exit_code=0 # Success exit code
1142
1188
  )
1143
- except Exception as e:
1144
- # Don't fail the entire operation if test execution fails
1145
- print(f"Warning: Post-fix test execution failed: {e}")
1189
+ run_report_file = META_DIR / f"{basename}_{language}_run.json"
1190
+ META_DIR.mkdir(parents=True, exist_ok=True)
1191
+ with open(run_report_file, 'w') as f:
1192
+ json.dump(asdict(run_report), f, indent=2, default=str)
1193
+ print(f"Updated run report to reflect fix success: {run_report_file}")
1194
+ except Exception as e:
1195
+ print(f"Warning: Could not update run report after successful fix: {e}")
1196
+ else:
1197
+ # If fix failed, then re-run tests to get current state
1198
+ try:
1199
+ test_file = pdd_files['test']
1200
+ if test_file.exists():
1201
+ print(f"Fix operation failed for {basename}. Re-running tests to assess current state.")
1202
+ _execute_tests_and_create_run_report(
1203
+ test_file, basename, language, target_coverage
1204
+ )
1205
+ except Exception as e:
1206
+ print(f"Warning: Post-fix test execution failed: {e}")
1146
1207
  else:
1147
1208
  errors.append(f"Operation '{operation}' failed.")
1148
1209
  break
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdd-cli
3
- Version: 0.0.47
3
+ Version: 0.0.49
4
4
  Summary: PDD (Prompt-Driven Development) Command Line Interface
5
5
  Author: Greg Tanaka
6
6
  Author-email: glt@alumni.caltech.edu
@@ -31,13 +31,14 @@ Requires-Dist: nest_asyncio==1.6.0
31
31
  Requires-Dist: pandas==2.2.3
32
32
  Requires-Dist: psutil==5.9.0
33
33
  Requires-Dist: pydantic==2.11.2
34
- Requires-Dist: litellm
34
+ Requires-Dist: litellm[caching]>=1.75.5
35
35
  Requires-Dist: rich==14.0.0
36
36
  Requires-Dist: semver==3.0.2
37
37
  Requires-Dist: setuptools
38
38
  Requires-Dist: pytest
39
39
  Requires-Dist: boto3==1.35.99
40
40
  Requires-Dist: python-Levenshtein
41
+ Requires-Dist: openai>=1.99.5
41
42
  Provides-Extra: dev
42
43
  Requires-Dist: commitizen; extra == "dev"
43
44
  Requires-Dist: pytest-cov; extra == "dev"
@@ -46,7 +47,7 @@ Requires-Dist: pytest-asyncio; extra == "dev"
46
47
  Requires-Dist: z3-solver; extra == "dev"
47
48
  Dynamic: license-file
48
49
 
49
- .. image:: https://img.shields.io/badge/pdd--cli-v0.0.47-blue
50
+ .. image:: https://img.shields.io/badge/pdd--cli-v0.0.49-blue
50
51
  :alt: PDD-CLI Version
51
52
 
52
53
  .. image:: https://img.shields.io/badge/Discord-join%20chat-7289DA.svg?logo=discord&logoColor=white&link=https://discord.gg/Yp4RTh8bG7
@@ -123,7 +124,7 @@ After installation, verify:
123
124
 
124
125
  pdd --version
125
126
 
126
- You'll see the current PDD version (e.g., 0.0.47).
127
+ You'll see the current PDD version (e.g., 0.0.49).
127
128
 
128
129
  Getting Started with Examples
129
130
  -----------------------------
@@ -1,4 +1,4 @@
1
- pdd/__init__.py,sha256=Dkuzn87nsXjYF_y3VgQ68D-o1f8igs1t1aCbHpy49KU,634
1
+ pdd/__init__.py,sha256=XJgiiw7J4yF8hdCjnIIweAshNfxsBs1CSWV_5_BD3x0,634
2
2
  pdd/auto_deps_main.py,sha256=iV2khcgSejiXjh5hiQqeu_BJQOLfTKXhMx14j6vRlf8,3916
3
3
  pdd/auto_include.py,sha256=OJcdcwTwJNqHPHKG9P4m9Ij-PiLex0EbuwJP0uiQi_Y,7484
4
4
  pdd/auto_update.py,sha256=w6jzTnMiYRNpwQHQxWNiIAwQ0d6xh1iOB3xgDsabWtc,5236
@@ -7,15 +7,15 @@ pdd/bug_to_unit_test.py,sha256=3qNz96bS1JyjKZzxUs1oIfzuLsPc8S29WmOfIKQaQ8Y,6599
7
7
  pdd/change.py,sha256=Hg_x0pa370-e6oDiczaTgFAy3Am9ReCPkqFrvqv4U38,6114
8
8
  pdd/change_main.py,sha256=oTQz9DUy6pIqq5CJzHIk01NrC88Xrm4FNEu0e-1Hx5Y,27748
9
9
  pdd/cli.py,sha256=sC86ii4kjc474taTwLkUPChkS4sD_Kdpa8O-jCBLV6w,43307
10
- pdd/cmd_test_main.py,sha256=JA6pzC-JEE6pVUZrDjbKvPduYAGzxbHep-3zYOkfEms,7299
10
+ pdd/cmd_test_main.py,sha256=5ftxDNNklDlHodkW8Rluvo3NKMHyMNhumG7G8mSoM9g,7716
11
11
  pdd/code_generator.py,sha256=KwbLgMfEER-qebGJdk5i25Qj3XdnHkVttjBlEeDasHs,4651
12
- pdd/code_generator_main.py,sha256=7MlZPmET3xycXL3VMLW8mLI8sW3pwRDGNyFynHt8TfM,25402
12
+ pdd/code_generator_main.py,sha256=whj_IaqoU-OQR9CW9rFRGzdua7cr9YnIuDsnmscE2jY,25815
13
13
  pdd/comment_line.py,sha256=sX2hf4bG1fILi_rvI9MkkwCZ2IitgKkW7nOiw8aQKPY,1845
14
14
  pdd/conflicts_in_prompts.py,sha256=9N3rZWdJUGayOTOgnHW9G_Jm1C9G4Y8hSLhnURc1BkY,4890
15
15
  pdd/conflicts_main.py,sha256=U23aJqJ6pgLDDCz-AaejWnG-qsTGAhZ9024KsHR9pYU,3650
16
- pdd/construct_paths.py,sha256=-oWSXURIDaWeQvM7uQGUote0hyf6ryD_fsTjEeEq5NI,31946
16
+ pdd/construct_paths.py,sha256=aG1v0NR-FIAQPLzhnD5mY7Rd_D_21fmzmM4x8Mj9hw8,30962
17
17
  pdd/context_generator.py,sha256=e5ey0i7wWnxAUiwiw1gkB1_t9OFjKU2lxYKpb_eVSio,6036
18
- pdd/context_generator_main.py,sha256=nJjc5L_pWTkDQzGhkHbm_C74TpgLeWGukdrb1rA5kEA,3857
18
+ pdd/context_generator_main.py,sha256=3riIDV2QskAMP-eq-uFicML79zQ5X-KMdw91g1RbP4E,4094
19
19
  pdd/continue_generation.py,sha256=6W2LQuQHWHSByv6zMMAVlGOCC1zEF_BAXwLPugMaC7M,5637
20
20
  pdd/crash_main.py,sha256=BmTbFSrEAICS-Ji7sTFI9SHpTTUZot16918wiypNnhc,7611
21
21
  pdd/detect_change.py,sha256=mA6k62xqeU1UG__CjzveJK0JDiRAO7AAC-JUfS0i2HQ,5510
@@ -24,7 +24,7 @@ pdd/edit_file.py,sha256=-FhZ-KGKYkPbnt0zFiDnnosPLh3bbKmften0Ios4-90,35017
24
24
  pdd/find_section.py,sha256=lz_FPY4KDCRAGlL1pWVZiutUNv7E4KsDFK-ymDWA_Ec,962
25
25
  pdd/fix_code_loop.py,sha256=LQXYQuFMjMM4yo6oJaFKyCg9OHpFwATp6QeHm8TsGR4,24468
26
26
  pdd/fix_code_module_errors.py,sha256=jKH88KunVhof1MYRI_F42_YnLt5k4lif4YztQgzB9g8,5446
27
- pdd/fix_error_loop.py,sha256=foWAQ_iMnpFL05ad1_wchGzWjIqLEL5AHp97NFfbt4Q,26092
27
+ pdd/fix_error_loop.py,sha256=lhJrfJuFi_dB7hWTbpJfLEEZaltaRXYDl0XScDLvbdk,26464
28
28
  pdd/fix_errors_from_unit_tests.py,sha256=fIqEfVIEx8PPSAzWu5nhin_remKu4c0_o51AN3g_x6s,9398
29
29
  pdd/fix_main.py,sha256=7TbHVUM2HuzCVMY-B2iHzvy5YEnKaaSbU1ZzXN7YG3U,14004
30
30
  pdd/fix_verification_errors.py,sha256=HvqGGdQqHq7OERmzcYP8Ft5nX_xthwVPJPG-YLv6VNM,17444
@@ -41,7 +41,7 @@ pdd/increase_tests.py,sha256=68cM9d1CpaLLm2ISFpJw39xbRjsfwxwS06yAwRoUCHk,4433
41
41
  pdd/incremental_code_generator.py,sha256=cWo3DJ0PybnrepFEAMibGjTVY3T8mLVvPt5W8cNhuxU,9402
42
42
  pdd/insert_includes.py,sha256=hNn8muRULiq3YMNI4W4pEPeM1ckiZ-EgR9WtCyWQ1eQ,5533
43
43
  pdd/install_completion.py,sha256=bLMJuMOBDvsEnDAUpgiPesNRGhY_IvBvz8ZvmbTzP4o,5472
44
- pdd/llm_invoke.py,sha256=hmgYShoBzVwil4Tjc7xa05onoT0g04sgNI65wdbT6VY,75010
44
+ pdd/llm_invoke.py,sha256=B5DwM4x8N191ehklb_WwahFiNBjEVKFcp8RPetWZl3Y,75892
45
45
  pdd/load_prompt_template.py,sha256=4NH8_t5eon_vcyTznqtemJ_yAPkTJm_hSdTRgzj3qEQ,1907
46
46
  pdd/logo_animation.py,sha256=n6HJWzuFze2csAAW2-zbxfjvWFYRI4hIdwVBtHBOkj4,20782
47
47
  pdd/mcp_config.json,sha256=D3ctWHlShvltbtH37zbYb6smVE0V80_lGjDKDIqsSBE,124
@@ -61,7 +61,7 @@ pdd/summarize_directory.py,sha256=cRKIVRWcti9SGLDuc40tsNbho7CdVbpWhlI-PoVC7xI,95
61
61
  pdd/sync_animation.py,sha256=e7Qb4m70BHYpl37CuuF-95j-APctPL4Zm_o1PSTTRFQ,28070
62
62
  pdd/sync_determine_operation.py,sha256=16Co4_IE0AZBLPdICi2MqW3730hiyLdqOf2kZcQA2cc,59590
63
63
  pdd/sync_main.py,sha256=2XUZZL9oIiNVsVohdsMpvrNoV8XkXhEKyt5bb2HlNHI,13641
64
- pdd/sync_orchestration.py,sha256=MG8mjqsZJHIPJqx6VfavJoF6IXn2R1ZrIG1maaERBjA,64153
64
+ pdd/sync_orchestration.py,sha256=FizZkwWPj30WCRVZIVKoyRXR3IxTC0LbcroMenq3xlQ,68320
65
65
  pdd/trace.py,sha256=oXHbOMfxeso7m81N5V2ixS_l6BPAlZrH6vifn0IgWbo,5225
66
66
  pdd/trace_main.py,sha256=Z8m8UgRZoaojX_H6aDDU7_lB7WNCLwZpFxbPTm1s-6s,4902
67
67
  pdd/track_cost.py,sha256=VIrHYh4i2G5T5Dq1plxwuzsG4OrHQgO0GPgFckgsQ_4,3266
@@ -108,9 +108,9 @@ pdd/prompts/trim_results_start_LLM.prompt,sha256=OKz8fAf1cYWKWgslFOHEkUpfaUDARh3
108
108
  pdd/prompts/unfinished_prompt_LLM.prompt,sha256=-JgBpiPTQZdWOAwOG1XpfpD9waynFTAT3Jo84eQ4bTw,1543
109
109
  pdd/prompts/update_prompt_LLM.prompt,sha256=prIc8uLp2jqnLTHt6JvWDZGanPZipivhhYeXe0lVaYw,1328
110
110
  pdd/prompts/xml_convertor_LLM.prompt,sha256=YGRGXJeg6EhM9690f-SKqQrKqSJjLFD51UrPOlO0Frg,2786
111
- pdd_cli-0.0.47.dist-info/licenses/LICENSE,sha256=-1bjYH-CEjGEQ8VixtnRYuu37kN6F9NxmZSDkBuUQ9o,1062
112
- pdd_cli-0.0.47.dist-info/METADATA,sha256=Mq_ZHelL5ASkEOb5k0AHi-Nr2KoFYvhZRJjbC69ygWo,12399
113
- pdd_cli-0.0.47.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
114
- pdd_cli-0.0.47.dist-info/entry_points.txt,sha256=Kr8HtNVb8uHZtQJNH4DnF8j7WNgWQbb7_Pw5hECSR-I,36
115
- pdd_cli-0.0.47.dist-info/top_level.txt,sha256=xjnhIACeMcMeDfVNREgQZl4EbTni2T11QkL5r7E-sbE,4
116
- pdd_cli-0.0.47.dist-info/RECORD,,
111
+ pdd_cli-0.0.49.dist-info/licenses/LICENSE,sha256=-1bjYH-CEjGEQ8VixtnRYuu37kN6F9NxmZSDkBuUQ9o,1062
112
+ pdd_cli-0.0.49.dist-info/METADATA,sha256=oOFDn3p_wev8d-q6JuhsPX6UQpdzN9Hb5feLaP7xEx0,12446
113
+ pdd_cli-0.0.49.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
114
+ pdd_cli-0.0.49.dist-info/entry_points.txt,sha256=Kr8HtNVb8uHZtQJNH4DnF8j7WNgWQbb7_Pw5hECSR-I,36
115
+ pdd_cli-0.0.49.dist-info/top_level.txt,sha256=xjnhIACeMcMeDfVNREgQZl4EbTni2T11QkL5r7E-sbE,4
116
+ pdd_cli-0.0.49.dist-info/RECORD,,