pdd-cli 0.0.46__py3-none-any.whl → 0.0.48__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.

@@ -142,6 +142,11 @@ def summarize_directory(
142
142
  file_mod_times = {f: os.path.getmtime(f) for f in files}
143
143
 
144
144
  for file_path in track(files, description="Processing files..."):
145
+ # Skip directories
146
+ if os.path.isdir(file_path):
147
+ if verbose:
148
+ print(f"[yellow]Skipping directory: {file_path}[/yellow]")
149
+ continue
145
150
  try:
146
151
  relative_path = os.path.relpath(file_path)
147
152
  normalized_path = normalize_path(relative_path)
@@ -211,49 +211,110 @@ def get_extension(language: str) -> str:
211
211
 
212
212
  def get_pdd_file_paths(basename: str, language: str, prompts_dir: str = "prompts") -> Dict[str, Path]:
213
213
  """Returns a dictionary mapping file types to their expected Path objects."""
214
+ import logging
215
+ logger = logging.getLogger(__name__)
216
+ logger.info(f"get_pdd_file_paths called: basename={basename}, language={language}, prompts_dir={prompts_dir}")
217
+
214
218
  try:
215
219
  # Use construct_paths to get configuration-aware paths
216
220
  prompt_filename = f"{basename}_{language}.prompt"
217
221
  prompt_path = str(Path(prompts_dir) / prompt_filename)
222
+ logger.info(f"Checking prompt_path={prompt_path}, exists={Path(prompt_path).exists()}")
218
223
 
219
- # Check if prompt file exists - if not, we can't proceed with construct_paths
224
+ # Check if prompt file exists - if not, we still need configuration-aware paths
220
225
  if not Path(prompt_path).exists():
221
- # Fall back to default path construction if prompt doesn't exist
226
+ # Use construct_paths with minimal inputs to get configuration-aware paths
227
+ # even when prompt doesn't exist
222
228
  extension = get_extension(language)
223
- return {
224
- 'prompt': Path(prompt_path),
225
- 'code': Path(f"{basename}.{extension}"),
226
- 'example': Path(f"{basename}_example.{extension}"),
227
- 'test': Path(f"test_{basename}.{extension}")
228
- }
229
+ try:
230
+ # Call construct_paths with empty input_file_paths to get configured output paths
231
+ resolved_config, _, output_paths, _ = construct_paths(
232
+ input_file_paths={}, # Empty dict since files don't exist yet
233
+ force=True,
234
+ quiet=True,
235
+ command="sync",
236
+ command_options={"basename": basename, "language": language}
237
+ )
238
+
239
+ import logging
240
+ logger = logging.getLogger(__name__)
241
+ logger.info(f"resolved_config: {resolved_config}")
242
+ logger.info(f"output_paths: {output_paths}")
243
+
244
+ # Extract directory configuration from resolved_config
245
+ test_dir = resolved_config.get('test_output_path', 'tests/')
246
+ example_dir = resolved_config.get('example_output_path', 'examples/')
247
+ code_dir = resolved_config.get('generate_output_path', './')
248
+
249
+ logger.info(f"Extracted dirs - test: {test_dir}, example: {example_dir}, code: {code_dir}")
250
+
251
+ # Ensure directories end with /
252
+ if test_dir and not test_dir.endswith('/'):
253
+ test_dir = test_dir + '/'
254
+ if example_dir and not example_dir.endswith('/'):
255
+ example_dir = example_dir + '/'
256
+ if code_dir and not code_dir.endswith('/'):
257
+ code_dir = code_dir + '/'
258
+
259
+ # Construct the full paths
260
+ test_path = f"{test_dir}test_{basename}.{extension}"
261
+ example_path = f"{example_dir}{basename}_example.{extension}"
262
+ code_path = f"{code_dir}{basename}.{extension}"
263
+
264
+ logger.debug(f"Final paths: test={test_path}, example={example_path}, code={code_path}")
265
+
266
+ # Convert to Path objects
267
+ test_path = Path(test_path)
268
+ example_path = Path(example_path)
269
+ code_path = Path(code_path)
270
+
271
+ result = {
272
+ 'prompt': Path(prompt_path),
273
+ 'code': code_path,
274
+ 'example': example_path,
275
+ 'test': test_path
276
+ }
277
+ logger.debug(f"get_pdd_file_paths returning (prompt missing): test={test_path}")
278
+ return result
279
+ except Exception as e:
280
+ # If construct_paths fails, fall back to current directory paths
281
+ # This maintains backward compatibility
282
+ import logging
283
+ logger = logging.getLogger(__name__)
284
+ logger.debug(f"construct_paths failed for non-existent prompt, using defaults: {e}")
285
+ return {
286
+ 'prompt': Path(prompt_path),
287
+ 'code': Path(f"{basename}.{extension}"),
288
+ 'example': Path(f"{basename}_example.{extension}"),
289
+ 'test': Path(f"test_{basename}.{extension}")
290
+ }
229
291
 
230
292
  input_file_paths = {
231
293
  "prompt_file": prompt_path
232
294
  }
233
295
 
234
- # Only call construct_paths if the prompt file exists
296
+ # Call construct_paths to get configuration-aware paths
235
297
  resolved_config, input_strings, output_file_paths, detected_language = construct_paths(
236
298
  input_file_paths=input_file_paths,
237
299
  force=True, # Use force=True to avoid interactive prompts during sync
238
300
  quiet=True,
239
- command="generate",
240
- command_options={}
301
+ command="sync", # Use sync command to get more tolerant path handling
302
+ command_options={"basename": basename, "language": language}
241
303
  )
242
304
 
243
- # Extract paths from config as specified in the spec
244
- # The spec shows: return { 'prompt': Path(config['prompt_file']), ... }
245
- # But we need to map the output_file_paths keys to our expected structure
246
-
247
- # For generate command, construct_paths returns these in output_file_paths:
248
- # - 'output' or 'code_file' for the generated code
249
- # For other commands, we need to construct the full set of paths
250
-
251
- # Get the code file path from output_file_paths
252
- code_path = output_file_paths.get('output', output_file_paths.get('code_file', ''))
305
+ # For sync command, output_file_paths contains the configured paths
306
+ # Extract the code path from output_file_paths
307
+ code_path = output_file_paths.get('generate_output_path', '')
308
+ if not code_path:
309
+ # Try other possible keys
310
+ code_path = output_file_paths.get('output', output_file_paths.get('code_file', ''))
253
311
  if not code_path:
254
- # Fallback to constructing from basename
312
+ # Fallback to constructing from basename with configuration
255
313
  extension = get_extension(language)
256
- code_path = f"{basename}.{extension}"
314
+ code_dir = resolved_config.get('generate_output_path', './')
315
+ if code_dir and not code_dir.endswith('/'):
316
+ code_dir = code_dir + '/'
317
+ code_path = f"{code_dir}{basename}.{extension}"
257
318
 
258
319
  # Get configured paths for example and test files using construct_paths
259
320
  # Note: construct_paths requires files to exist, so we need to handle the case
@@ -275,12 +336,16 @@ def get_pdd_file_paths(basename: str, language: str, prompts_dir: str = "prompts
275
336
  )
276
337
  example_path = Path(example_output_paths.get('output', f"{basename}_example.{get_extension(language)}"))
277
338
 
278
- # Get test path using test command
279
- _, _, test_output_paths, _ = construct_paths(
280
- input_file_paths={"prompt_file": prompt_path, "code_file": code_path},
281
- force=True, quiet=True, command="test", command_options={}
282
- )
283
- test_path = Path(test_output_paths.get('output', f"test_{basename}.{get_extension(language)}"))
339
+ # Get test path using test command - handle case where test file doesn't exist yet
340
+ try:
341
+ _, _, test_output_paths, _ = construct_paths(
342
+ input_file_paths={"prompt_file": prompt_path, "code_file": code_path},
343
+ force=True, quiet=True, command="test", command_options={}
344
+ )
345
+ test_path = Path(test_output_paths.get('output', f"test_{basename}.{get_extension(language)}"))
346
+ except FileNotFoundError:
347
+ # Test file doesn't exist yet - create default path
348
+ test_path = Path(f"test_{basename}.{get_extension(language)}")
284
349
 
285
350
  finally:
286
351
  # Clean up temporary file if we created it
@@ -304,11 +369,15 @@ def get_pdd_file_paths(basename: str, language: str, prompts_dir: str = "prompts
304
369
  )
305
370
  example_path = Path(example_output_paths.get('output', f"{basename}_example.{get_extension(language)}"))
306
371
 
307
- _, _, test_output_paths, _ = construct_paths(
308
- input_file_paths={"prompt_file": prompt_path},
309
- force=True, quiet=True, command="test", command_options={}
310
- )
311
- test_path = Path(test_output_paths.get('output', f"test_{basename}.{get_extension(language)}"))
372
+ try:
373
+ _, _, test_output_paths, _ = construct_paths(
374
+ input_file_paths={"prompt_file": prompt_path},
375
+ force=True, quiet=True, command="test", command_options={}
376
+ )
377
+ test_path = Path(test_output_paths.get('output', f"test_{basename}.{get_extension(language)}"))
378
+ except Exception:
379
+ # If test path construction fails, use default naming
380
+ test_path = Path(f"test_{basename}.{get_extension(language)}")
312
381
 
313
382
  except Exception:
314
383
  # Final fallback to deriving from code path if all else fails
@@ -319,9 +388,15 @@ def get_pdd_file_paths(basename: str, language: str, prompts_dir: str = "prompts
319
388
  example_path = code_dir / f"{code_stem}_example{code_ext}"
320
389
  test_path = code_dir / f"test_{code_stem}{code_ext}"
321
390
 
391
+ # Ensure all paths are Path objects
392
+ if isinstance(code_path, str):
393
+ code_path = Path(code_path)
394
+
395
+ # Keep paths as they are (absolute or relative as returned by construct_paths)
396
+ # This ensures consistency with how construct_paths expects them
322
397
  return {
323
398
  'prompt': Path(prompt_path),
324
- 'code': Path(code_path),
399
+ 'code': code_path,
325
400
  'example': example_path,
326
401
  'test': test_path
327
402
  }
@@ -752,22 +827,8 @@ def _perform_sync_analysis(basename: str, language: str, target_coverage: float,
752
827
 
753
828
  run_report = read_run_report(basename, language)
754
829
  if run_report:
755
- # Check test failures first (higher priority than exit code)
756
- if run_report.tests_failed > 0:
757
- return SyncDecision(
758
- operation='fix',
759
- reason=f'Test failures detected: {run_report.tests_failed} failed tests',
760
- confidence=0.90,
761
- estimated_cost=estimate_operation_cost('fix'),
762
- details={
763
- 'decision_type': 'heuristic',
764
- 'tests_failed': run_report.tests_failed,
765
- 'exit_code': run_report.exit_code,
766
- 'coverage': run_report.coverage
767
- }
768
- )
769
-
770
- # Check if we just completed a crash operation and need verification
830
+ # Check if we just completed a crash operation and need verification FIRST
831
+ # This takes priority over test failures because we need to verify the crash fix worked
771
832
  if fingerprint and fingerprint.command == 'crash' and not skip_verify:
772
833
  return SyncDecision(
773
834
  operation='verify',
@@ -782,6 +843,41 @@ def _perform_sync_analysis(basename: str, language: str, target_coverage: float,
782
843
  }
783
844
  )
784
845
 
846
+ # Check test failures (after crash verification check)
847
+ if run_report.tests_failed > 0:
848
+ # First check if the test file actually exists
849
+ pdd_files = get_pdd_file_paths(basename, language, prompts_dir)
850
+ test_file = pdd_files.get('test')
851
+
852
+ # Only suggest 'fix' if test file exists
853
+ if test_file and test_file.exists():
854
+ return SyncDecision(
855
+ operation='fix',
856
+ reason=f'Test failures detected: {run_report.tests_failed} failed tests',
857
+ confidence=0.90,
858
+ estimated_cost=estimate_operation_cost('fix'),
859
+ details={
860
+ 'decision_type': 'heuristic',
861
+ 'tests_failed': run_report.tests_failed,
862
+ 'exit_code': run_report.exit_code,
863
+ 'coverage': run_report.coverage
864
+ }
865
+ )
866
+ # If test file doesn't exist but we have test failures in run report,
867
+ # we need to generate the test first
868
+ else:
869
+ return SyncDecision(
870
+ operation='test',
871
+ reason='Test failures reported but test file missing - need to generate tests',
872
+ confidence=0.85,
873
+ estimated_cost=estimate_operation_cost('test'),
874
+ details={
875
+ 'decision_type': 'heuristic',
876
+ 'run_report_shows_failures': True,
877
+ 'test_file_exists': False
878
+ }
879
+ )
880
+
785
881
  # Then check for runtime crashes (only if no test failures)
786
882
  if run_report.exit_code != 0:
787
883
  # Context-aware decision: prefer 'fix' over 'crash' when example has run successfully before
@@ -1023,6 +1119,22 @@ def _perform_sync_analysis(basename: str, language: str, target_coverage: float,
1023
1119
  # Some files are missing but no changes detected
1024
1120
  if not paths['code'].exists():
1025
1121
  if paths['prompt'].exists():
1122
+ # CRITICAL FIX: Check if auto-deps was just completed to prevent infinite loop
1123
+ if fingerprint and fingerprint.command == 'auto-deps':
1124
+ return SyncDecision(
1125
+ operation='generate',
1126
+ reason='Auto-deps completed, now generate missing code file',
1127
+ confidence=0.90,
1128
+ estimated_cost=estimate_operation_cost('generate'),
1129
+ details={
1130
+ 'decision_type': 'heuristic',
1131
+ 'prompt_path': str(paths['prompt']),
1132
+ 'code_exists': False,
1133
+ 'auto_deps_completed': True,
1134
+ 'previous_command': fingerprint.command
1135
+ }
1136
+ )
1137
+
1026
1138
  prompt_content = paths['prompt'].read_text(encoding='utf-8', errors='ignore')
1027
1139
  if check_for_dependencies(prompt_content):
1028
1140
  return SyncDecision(