pdd-cli 0.0.46__py3-none-any.whl → 0.0.47__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 +1 -1
- pdd/cli.py +42 -0
- pdd/cmd_test_main.py +5 -0
- pdd/construct_paths.py +16 -7
- pdd/fix_error_loop.py +36 -5
- pdd/llm_invoke.py +45 -7
- pdd/prompts/auto_include_LLM.prompt +51 -905
- pdd/summarize_directory.py +5 -0
- pdd/sync_determine_operation.py +163 -51
- pdd/sync_orchestration.py +213 -49
- pdd/update_model_costs.py +2 -2
- {pdd_cli-0.0.46.dist-info → pdd_cli-0.0.47.dist-info}/METADATA +3 -3
- {pdd_cli-0.0.46.dist-info → pdd_cli-0.0.47.dist-info}/RECORD +17 -17
- {pdd_cli-0.0.46.dist-info → pdd_cli-0.0.47.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.46.dist-info → pdd_cli-0.0.47.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.46.dist-info → pdd_cli-0.0.47.dist-info}/licenses/LICENSE +0 -0
- {pdd_cli-0.0.46.dist-info → pdd_cli-0.0.47.dist-info}/top_level.txt +0 -0
pdd/summarize_directory.py
CHANGED
|
@@ -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)
|
pdd/sync_determine_operation.py
CHANGED
|
@@ -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
|
|
224
|
+
# Check if prompt file exists - if not, we still need configuration-aware paths
|
|
220
225
|
if not Path(prompt_path).exists():
|
|
221
|
-
#
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
#
|
|
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="
|
|
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
|
-
#
|
|
244
|
-
#
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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':
|
|
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
|
|
756
|
-
|
|
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(
|