pdd-cli 0.0.40__py3-none-any.whl → 0.0.42__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.
- pdd/__init__.py +1 -1
- pdd/auto_deps_main.py +1 -1
- pdd/auto_update.py +73 -78
- pdd/bug_main.py +3 -3
- pdd/bug_to_unit_test.py +46 -38
- pdd/change.py +20 -13
- pdd/change_main.py +223 -163
- pdd/cli.py +192 -95
- pdd/cmd_test_main.py +51 -36
- pdd/code_generator_main.py +3 -2
- pdd/conflicts_main.py +1 -1
- pdd/construct_paths.py +221 -19
- pdd/context_generator_main.py +27 -12
- pdd/crash_main.py +44 -50
- pdd/data/llm_model.csv +1 -1
- pdd/detect_change_main.py +1 -1
- pdd/fix_code_module_errors.py +12 -0
- pdd/fix_main.py +2 -2
- pdd/fix_verification_errors.py +13 -0
- pdd/fix_verification_main.py +3 -3
- pdd/generate_output_paths.py +113 -21
- pdd/generate_test.py +53 -16
- pdd/llm_invoke.py +162 -0
- pdd/logo_animation.py +455 -0
- pdd/preprocess_main.py +1 -1
- pdd/process_csv_change.py +1 -1
- pdd/prompts/extract_program_code_fix_LLM.prompt +2 -1
- pdd/prompts/sync_analysis_LLM.prompt +82 -0
- pdd/split_main.py +1 -1
- pdd/sync_animation.py +643 -0
- pdd/sync_determine_operation.py +1039 -0
- pdd/sync_main.py +333 -0
- pdd/sync_orchestration.py +639 -0
- pdd/trace_main.py +1 -1
- pdd/update_main.py +7 -2
- pdd/xml_tagger.py +15 -6
- pdd_cli-0.0.42.dist-info/METADATA +307 -0
- {pdd_cli-0.0.40.dist-info → pdd_cli-0.0.42.dist-info}/RECORD +42 -36
- pdd_cli-0.0.40.dist-info/METADATA +0 -269
- {pdd_cli-0.0.40.dist-info → pdd_cli-0.0.42.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.40.dist-info → pdd_cli-0.0.42.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.40.dist-info → pdd_cli-0.0.42.dist-info}/licenses/LICENSE +0 -0
- {pdd_cli-0.0.40.dist-info → pdd_cli-0.0.42.dist-info}/top_level.txt +0 -0
pdd/construct_paths.py
CHANGED
|
@@ -4,9 +4,12 @@ from __future__ import annotations
|
|
|
4
4
|
import sys
|
|
5
5
|
import os
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Dict, Tuple, Any, Optional
|
|
7
|
+
from typing import Dict, Tuple, Any, Optional, List
|
|
8
|
+
import fnmatch
|
|
9
|
+
import logging
|
|
8
10
|
|
|
9
11
|
import click
|
|
12
|
+
import yaml
|
|
10
13
|
from rich.console import Console
|
|
11
14
|
from rich.theme import Theme
|
|
12
15
|
|
|
@@ -21,6 +24,114 @@ import csv
|
|
|
21
24
|
|
|
22
25
|
console = Console(theme=Theme({"info": "cyan", "warning": "yellow", "error": "bold red"}))
|
|
23
26
|
|
|
27
|
+
# Configuration loading functions
|
|
28
|
+
def _find_pddrc_file(start_path: Optional[Path] = None) -> Optional[Path]:
|
|
29
|
+
"""Find .pddrc file by searching upward from the given path."""
|
|
30
|
+
if start_path is None:
|
|
31
|
+
start_path = Path.cwd()
|
|
32
|
+
|
|
33
|
+
# Search upward through parent directories
|
|
34
|
+
for path in [start_path] + list(start_path.parents):
|
|
35
|
+
pddrc_file = path / ".pddrc"
|
|
36
|
+
if pddrc_file.is_file():
|
|
37
|
+
return pddrc_file
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
def _load_pddrc_config(pddrc_path: Path) -> Dict[str, Any]:
|
|
41
|
+
"""Load and parse .pddrc configuration file."""
|
|
42
|
+
try:
|
|
43
|
+
with open(pddrc_path, 'r', encoding='utf-8') as f:
|
|
44
|
+
config = yaml.safe_load(f)
|
|
45
|
+
|
|
46
|
+
if not isinstance(config, dict):
|
|
47
|
+
raise ValueError(f"Invalid .pddrc format: expected dictionary at root level")
|
|
48
|
+
|
|
49
|
+
# Validate basic structure
|
|
50
|
+
if 'contexts' not in config:
|
|
51
|
+
raise ValueError(f"Invalid .pddrc format: missing 'contexts' section")
|
|
52
|
+
|
|
53
|
+
return config
|
|
54
|
+
except yaml.YAMLError as e:
|
|
55
|
+
raise ValueError(f"YAML syntax error in .pddrc: {e}")
|
|
56
|
+
except Exception as e:
|
|
57
|
+
raise ValueError(f"Error loading .pddrc: {e}")
|
|
58
|
+
|
|
59
|
+
def _detect_context(current_dir: Path, config: Dict[str, Any], context_override: Optional[str] = None) -> Optional[str]:
|
|
60
|
+
"""Detect the appropriate context based on current directory path."""
|
|
61
|
+
if context_override:
|
|
62
|
+
# Validate that the override context exists
|
|
63
|
+
contexts = config.get('contexts', {})
|
|
64
|
+
if context_override not in contexts:
|
|
65
|
+
available = list(contexts.keys())
|
|
66
|
+
raise ValueError(f"Unknown context '{context_override}'. Available contexts: {available}")
|
|
67
|
+
return context_override
|
|
68
|
+
|
|
69
|
+
contexts = config.get('contexts', {})
|
|
70
|
+
current_path_str = str(current_dir)
|
|
71
|
+
|
|
72
|
+
# Try to match against each context's paths
|
|
73
|
+
for context_name, context_config in contexts.items():
|
|
74
|
+
if context_name == 'default':
|
|
75
|
+
continue # Handle default as fallback
|
|
76
|
+
|
|
77
|
+
paths = context_config.get('paths', [])
|
|
78
|
+
for path_pattern in paths:
|
|
79
|
+
# Convert glob pattern to match current directory
|
|
80
|
+
if fnmatch.fnmatch(current_path_str, f"*/{path_pattern}") or \
|
|
81
|
+
fnmatch.fnmatch(current_path_str, path_pattern) or \
|
|
82
|
+
current_path_str.endswith(f"/{path_pattern.rstrip('/**')}"):
|
|
83
|
+
return context_name
|
|
84
|
+
|
|
85
|
+
# Return default context if available
|
|
86
|
+
if 'default' in contexts:
|
|
87
|
+
return 'default'
|
|
88
|
+
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
def _get_context_config(config: Dict[str, Any], context_name: Optional[str]) -> Dict[str, Any]:
|
|
92
|
+
"""Get configuration settings for the specified context."""
|
|
93
|
+
if not context_name:
|
|
94
|
+
return {}
|
|
95
|
+
|
|
96
|
+
contexts = config.get('contexts', {})
|
|
97
|
+
context_config = contexts.get(context_name, {})
|
|
98
|
+
return context_config.get('defaults', {})
|
|
99
|
+
|
|
100
|
+
def _resolve_config_hierarchy(
|
|
101
|
+
cli_options: Dict[str, Any],
|
|
102
|
+
context_config: Dict[str, Any],
|
|
103
|
+
env_vars: Dict[str, str]
|
|
104
|
+
) -> Dict[str, Any]:
|
|
105
|
+
"""Apply configuration hierarchy: CLI > context > environment > defaults."""
|
|
106
|
+
resolved = {}
|
|
107
|
+
|
|
108
|
+
# Configuration keys to resolve
|
|
109
|
+
config_keys = {
|
|
110
|
+
'generate_output_path': 'PDD_GENERATE_OUTPUT_PATH',
|
|
111
|
+
'test_output_path': 'PDD_TEST_OUTPUT_PATH',
|
|
112
|
+
'example_output_path': 'PDD_EXAMPLE_OUTPUT_PATH',
|
|
113
|
+
'default_language': 'PDD_DEFAULT_LANGUAGE',
|
|
114
|
+
'target_coverage': 'PDD_TEST_COVERAGE_TARGET',
|
|
115
|
+
'strength': None,
|
|
116
|
+
'temperature': None,
|
|
117
|
+
'budget': None,
|
|
118
|
+
'max_attempts': None,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for config_key, env_var in config_keys.items():
|
|
122
|
+
# 1. CLI options (highest priority)
|
|
123
|
+
if config_key in cli_options and cli_options[config_key] is not None:
|
|
124
|
+
resolved[config_key] = cli_options[config_key]
|
|
125
|
+
# 2. Context configuration
|
|
126
|
+
elif config_key in context_config:
|
|
127
|
+
resolved[config_key] = context_config[config_key]
|
|
128
|
+
# 3. Environment variables
|
|
129
|
+
elif env_var and env_var in env_vars:
|
|
130
|
+
resolved[config_key] = env_vars[env_var]
|
|
131
|
+
# 4. Defaults are handled elsewhere
|
|
132
|
+
|
|
133
|
+
return resolved
|
|
134
|
+
|
|
24
135
|
|
|
25
136
|
def _read_file(path: Path) -> str:
|
|
26
137
|
"""Read a text file safely and return its contents."""
|
|
@@ -126,29 +237,24 @@ def _is_known_language(language_name: str) -> bool:
|
|
|
126
237
|
|
|
127
238
|
def _strip_language_suffix(path_like: os.PathLike[str]) -> str:
|
|
128
239
|
"""
|
|
129
|
-
Remove trailing '_<language
|
|
130
|
-
if it matches a known language.
|
|
240
|
+
Remove trailing '_<language>' from a filename stem if it matches a known language.
|
|
131
241
|
"""
|
|
132
242
|
p = Path(path_like)
|
|
133
|
-
stem = p.stem
|
|
243
|
+
stem = p.stem # removes last extension (e.g., '.prompt', '.py')
|
|
134
244
|
|
|
135
|
-
if "_" not in stem:
|
|
245
|
+
if "_" not in stem:
|
|
136
246
|
return stem
|
|
137
247
|
|
|
138
248
|
parts = stem.split("_")
|
|
139
|
-
# Avoid splitting single-word stems like "Makefile_" if that's possible
|
|
140
|
-
if len(parts) < 2:
|
|
141
|
-
return stem
|
|
142
|
-
|
|
143
249
|
candidate_lang = parts[-1]
|
|
144
250
|
|
|
145
|
-
# Check if the last part is a known language
|
|
146
251
|
if _is_known_language(candidate_lang):
|
|
147
|
-
#
|
|
252
|
+
# Do not strip '_prompt' from a non-.prompt file (e.g., 'test_prompt.txt')
|
|
253
|
+
if candidate_lang == 'prompt' and p.suffix != '.prompt':
|
|
254
|
+
return stem
|
|
148
255
|
return "_".join(parts[:-1])
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
return stem
|
|
256
|
+
|
|
257
|
+
return stem
|
|
152
258
|
|
|
153
259
|
|
|
154
260
|
def _extract_basename(
|
|
@@ -267,20 +373,101 @@ def construct_paths(
|
|
|
267
373
|
command: str,
|
|
268
374
|
command_options: Optional[Dict[str, Any]], # Allow None
|
|
269
375
|
create_error_file: bool = True, # Added parameter to control error file creation
|
|
270
|
-
|
|
376
|
+
context_override: Optional[str] = None, # Added parameter for context override
|
|
377
|
+
) -> Tuple[Dict[str, Any], Dict[str, str], Dict[str, str], str]:
|
|
271
378
|
"""
|
|
272
379
|
High‑level orchestrator that loads inputs, determines basename/language,
|
|
273
380
|
computes output locations, and verifies overwrite rules.
|
|
381
|
+
|
|
382
|
+
Supports .pddrc configuration with context-aware settings and configuration hierarchy:
|
|
383
|
+
CLI options > .pddrc context > environment variables > defaults
|
|
274
384
|
|
|
275
385
|
Returns
|
|
276
386
|
-------
|
|
277
|
-
(input_strings, output_file_paths, language)
|
|
387
|
+
(resolved_config, input_strings, output_file_paths, language)
|
|
278
388
|
"""
|
|
279
389
|
command_options = command_options or {} # Ensure command_options is a dict
|
|
280
390
|
|
|
391
|
+
# ------------- Load .pddrc configuration -----------------
|
|
392
|
+
pddrc_config = {}
|
|
393
|
+
context = None
|
|
394
|
+
context_config = {}
|
|
395
|
+
|
|
396
|
+
try:
|
|
397
|
+
# Find and load .pddrc file
|
|
398
|
+
pddrc_path = _find_pddrc_file()
|
|
399
|
+
if pddrc_path:
|
|
400
|
+
pddrc_config = _load_pddrc_config(pddrc_path)
|
|
401
|
+
|
|
402
|
+
# Detect appropriate context
|
|
403
|
+
current_dir = Path.cwd()
|
|
404
|
+
context = _detect_context(current_dir, pddrc_config, context_override)
|
|
405
|
+
|
|
406
|
+
# Get context-specific configuration
|
|
407
|
+
context_config = _get_context_config(pddrc_config, context)
|
|
408
|
+
|
|
409
|
+
if not quiet and context:
|
|
410
|
+
console.print(f"[info]Using .pddrc context:[/info] {context}")
|
|
411
|
+
|
|
412
|
+
# Apply configuration hierarchy
|
|
413
|
+
env_vars = dict(os.environ)
|
|
414
|
+
resolved_config = _resolve_config_hierarchy(command_options, context_config, env_vars)
|
|
415
|
+
|
|
416
|
+
# Update command_options with resolved configuration for internal use
|
|
417
|
+
for key, value in resolved_config.items():
|
|
418
|
+
if key not in command_options or command_options[key] is None:
|
|
419
|
+
command_options[key] = value
|
|
420
|
+
|
|
421
|
+
# Also update context_config with resolved environment variables for generate_output_paths
|
|
422
|
+
# This ensures environment variables are available when context config doesn't override them
|
|
423
|
+
for key, value in resolved_config.items():
|
|
424
|
+
if key.endswith('_output_path') and key not in context_config:
|
|
425
|
+
context_config[key] = value
|
|
426
|
+
|
|
427
|
+
except Exception as e:
|
|
428
|
+
error_msg = f"Configuration error: {e}"
|
|
429
|
+
console.print(f"[error]{error_msg}[/error]", style="error")
|
|
430
|
+
if not quiet:
|
|
431
|
+
console.print("[warning]Continuing with default configuration...[/warning]", style="warning")
|
|
432
|
+
# Initialize resolved_config on error to avoid downstream issues
|
|
433
|
+
resolved_config = command_options.copy()
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
# ------------- Handle sync discovery mode ----------------
|
|
437
|
+
if command == "sync" and not input_file_paths:
|
|
438
|
+
basename = command_options.get("basename")
|
|
439
|
+
if not basename:
|
|
440
|
+
raise ValueError("Basename must be provided in command_options for sync discovery mode.")
|
|
441
|
+
|
|
442
|
+
# For discovery, we only need directory paths. Call generate_output_paths with dummy values.
|
|
443
|
+
try:
|
|
444
|
+
output_paths_str = generate_output_paths(
|
|
445
|
+
command="sync",
|
|
446
|
+
output_locations={},
|
|
447
|
+
basename=basename,
|
|
448
|
+
language="python", # Dummy language
|
|
449
|
+
file_extension=".py", # Dummy extension
|
|
450
|
+
context_config=context_config,
|
|
451
|
+
)
|
|
452
|
+
# Infer base directories from a sample output path
|
|
453
|
+
gen_path = Path(output_paths_str.get("generate_output_path", "src"))
|
|
454
|
+
resolved_config["prompts_dir"] = str(gen_path.parent.parent / "prompts")
|
|
455
|
+
resolved_config["code_dir"] = str(gen_path.parent)
|
|
456
|
+
resolved_config["tests_dir"] = str(Path(output_paths_str.get("test_output_path", "tests")).parent)
|
|
457
|
+
resolved_config["examples_dir"] = str(Path(output_paths_str.get("example_output_path", "examples")).parent)
|
|
458
|
+
|
|
459
|
+
except Exception as e:
|
|
460
|
+
console.print(f"[error]Failed to determine initial paths for sync: {e}", style="error")
|
|
461
|
+
raise
|
|
462
|
+
|
|
463
|
+
# Return early for discovery mode
|
|
464
|
+
return resolved_config, {}, {}, ""
|
|
465
|
+
|
|
466
|
+
|
|
281
467
|
if not input_file_paths:
|
|
282
468
|
raise ValueError("No input files provided")
|
|
283
469
|
|
|
470
|
+
|
|
284
471
|
# ------------- normalise & resolve Paths -----------------
|
|
285
472
|
input_paths: Dict[str, Path] = {}
|
|
286
473
|
for key, path_str in input_file_paths.items():
|
|
@@ -407,6 +594,7 @@ def construct_paths(
|
|
|
407
594
|
basename=basename,
|
|
408
595
|
language=language,
|
|
409
596
|
file_extension=file_extension,
|
|
597
|
+
context_config=context_config,
|
|
410
598
|
)
|
|
411
599
|
# Convert to Path objects for internal use
|
|
412
600
|
output_paths_resolved: Dict[str, Path] = {k: Path(v) for k, v in output_paths_str.items()}
|
|
@@ -440,8 +628,12 @@ def construct_paths(
|
|
|
440
628
|
click.secho("Operation cancelled.", fg="red", err=True)
|
|
441
629
|
sys.exit(1) # Exit if user chooses not to overwrite
|
|
442
630
|
except Exception as e: # Catch potential errors during confirm (like EOFError in non-interactive)
|
|
443
|
-
|
|
444
|
-
|
|
631
|
+
if 'EOF' in str(e) or 'end-of-file' in str(e).lower():
|
|
632
|
+
# Non-interactive environment, default to not overwriting
|
|
633
|
+
click.secho("Non-interactive environment detected. Use --force to overwrite existing files.", fg="yellow", err=True)
|
|
634
|
+
else:
|
|
635
|
+
click.secho(f"Confirmation failed: {e}. Aborting.", fg="red", err=True)
|
|
636
|
+
sys.exit(1)
|
|
445
637
|
|
|
446
638
|
|
|
447
639
|
# ------------- Final reporting ---------------------------
|
|
@@ -462,4 +654,14 @@ def construct_paths(
|
|
|
462
654
|
# Since we converted to Path, convert back now.
|
|
463
655
|
output_file_paths_str_return = {k: str(v) for k, v in output_paths_resolved.items()}
|
|
464
656
|
|
|
465
|
-
|
|
657
|
+
# Add resolved paths to the config that gets returned
|
|
658
|
+
resolved_config.update(output_file_paths_str_return)
|
|
659
|
+
# Also add inferred directory paths
|
|
660
|
+
gen_path = Path(resolved_config.get("generate_output_path", "src"))
|
|
661
|
+
resolved_config["prompts_dir"] = str(next(iter(input_paths.values())).parent)
|
|
662
|
+
resolved_config["code_dir"] = str(gen_path.parent)
|
|
663
|
+
resolved_config["tests_dir"] = str(Path(resolved_config.get("test_output_path", "tests")).parent)
|
|
664
|
+
resolved_config["examples_dir"] = str(Path(resolved_config.get("example_output_path", "examples")).parent)
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
return resolved_config, input_strings, output_file_paths_str_return, language
|
pdd/context_generator_main.py
CHANGED
|
@@ -25,7 +25,7 @@ def context_generator_main(ctx: click.Context, prompt_file: str, code_file: str,
|
|
|
25
25
|
command_options = {
|
|
26
26
|
"output": output
|
|
27
27
|
}
|
|
28
|
-
input_strings, output_file_paths, language = construct_paths(
|
|
28
|
+
resolved_config, input_strings, output_file_paths, language = construct_paths(
|
|
29
29
|
input_file_paths=input_file_paths,
|
|
30
30
|
force=ctx.obj.get('force', False),
|
|
31
31
|
quiet=ctx.obj.get('quiet', False),
|
|
@@ -51,22 +51,37 @@ def context_generator_main(ctx: click.Context, prompt_file: str, code_file: str,
|
|
|
51
51
|
verbose=ctx.obj.get('verbose', False)
|
|
52
52
|
)
|
|
53
53
|
|
|
54
|
-
# Save results
|
|
55
|
-
|
|
56
|
-
|
|
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}")
|
|
59
|
+
if final_output_path and example_code is not None:
|
|
60
|
+
with open(final_output_path, 'w') as f:
|
|
57
61
|
f.write(example_code)
|
|
62
|
+
elif final_output_path and example_code is None:
|
|
63
|
+
# Log the error but don't crash
|
|
64
|
+
if not ctx.obj.get('quiet', False):
|
|
65
|
+
rprint("[bold red]Warning:[/bold red] Example generation failed, skipping file write")
|
|
58
66
|
|
|
59
67
|
# Provide user feedback
|
|
60
68
|
if not ctx.obj.get('quiet', False):
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
69
|
+
if example_code is not None:
|
|
70
|
+
rprint("[bold green]Example code generated successfully.[/bold green]")
|
|
71
|
+
rprint(f"[bold]Model used:[/bold] {model_name}")
|
|
72
|
+
rprint(f"[bold]Total cost:[/bold] ${total_cost:.6f}")
|
|
73
|
+
if final_output_path and example_code is not None:
|
|
74
|
+
rprint(f"[bold]Example code saved to:[/bold] {final_output_path}")
|
|
75
|
+
else:
|
|
76
|
+
rprint("[bold red]Example code generation failed.[/bold red]")
|
|
77
|
+
rprint(f"[bold]Total cost:[/bold] ${total_cost:.6f}")
|
|
66
78
|
|
|
67
|
-
# Always print example code, even in quiet mode
|
|
68
|
-
|
|
69
|
-
|
|
79
|
+
# Always print example code, even in quiet mode (if it exists)
|
|
80
|
+
if example_code is not None:
|
|
81
|
+
rprint("[bold]Generated Example Code:[/bold]")
|
|
82
|
+
rprint(example_code)
|
|
83
|
+
else:
|
|
84
|
+
rprint("[bold red]No example code generated due to errors.[/bold red]")
|
|
70
85
|
|
|
71
86
|
return example_code, total_cost, model_name
|
|
72
87
|
|
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))
|
|
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)
|
|
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,
|
|
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,77 +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
|
-
#
|
|
114
|
-
if
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
program_content, prompt_content, code_content, error_content,
|
|
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
|
|
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
|
-
|
|
132
|
-
|
|
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 AND content was updated
|
|
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
|
-
#
|
|
139
|
-
if output_code_path_str
|
|
138
|
+
# Always write output files if output paths are specified
|
|
139
|
+
if output_code_path_str:
|
|
140
140
|
output_code_path = Path(output_code_path_str)
|
|
141
|
-
output_code_path.parent.mkdir(parents=True, exist_ok=True)
|
|
141
|
+
output_code_path.parent.mkdir(parents=True, exist_ok=True)
|
|
142
142
|
with open(output_code_path, "w") as f:
|
|
143
143
|
f.write(final_code)
|
|
144
144
|
|
|
145
|
-
if output_program_path_str
|
|
145
|
+
if output_program_path_str:
|
|
146
146
|
output_program_path = Path(output_program_path_str)
|
|
147
|
-
output_program_path.parent.mkdir(parents=True, exist_ok=True)
|
|
147
|
+
output_program_path.parent.mkdir(parents=True, exist_ok=True)
|
|
148
148
|
with open(output_program_path, "w") as f:
|
|
149
149
|
f.write(final_program)
|
|
150
150
|
|
|
151
|
-
# Provide user feedback
|
|
152
151
|
if not quiet:
|
|
153
152
|
if success:
|
|
154
|
-
rprint("[bold green]Crash fix attempt completed.[/bold green]")
|
|
153
|
+
rprint("[bold green]Crash fix attempt completed.[/bold green]")
|
|
155
154
|
else:
|
|
156
|
-
rprint("[bold yellow]Crash fix attempt completed with issues
|
|
155
|
+
rprint("[bold yellow]Crash fix attempt completed with issues.[/bold yellow]")
|
|
157
156
|
rprint(f"[bold]Model used:[/bold] {model}")
|
|
158
157
|
rprint(f"[bold]Total attempts:[/bold] {attempts}")
|
|
159
158
|
rprint(f"[bold]Total cost:[/bold] ${cost:.2f}")
|
|
@@ -162,25 +161,20 @@ def crash_main(
|
|
|
162
161
|
if code_updated:
|
|
163
162
|
rprint(f"[bold]Fixed code saved to:[/bold] {output_code_path_str}")
|
|
164
163
|
else:
|
|
165
|
-
rprint(f"[info]Code file {Path(code_file).name} was not modified
|
|
166
|
-
|
|
164
|
+
rprint(f"[info]Code file '{Path(code_file).name}' was not modified (but output file was written).[/info]")
|
|
167
165
|
if output_program_path_str:
|
|
168
166
|
if program_updated:
|
|
169
167
|
rprint(f"[bold]Fixed program saved to:[/bold] {output_program_path_str}")
|
|
170
168
|
else:
|
|
171
|
-
rprint(f"[info]Program file {Path(program_file).name} was not modified
|
|
169
|
+
rprint(f"[info]Program file '{Path(program_file).name}' was not modified (but output file was written).[/info]")
|
|
172
170
|
|
|
173
171
|
return success, final_code, final_program, attempts, cost, model
|
|
174
|
-
|
|
172
|
+
|
|
175
173
|
except FileNotFoundError as e:
|
|
176
174
|
if not quiet:
|
|
177
|
-
|
|
178
|
-
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}")
|
|
179
176
|
sys.exit(1)
|
|
180
177
|
except Exception as e:
|
|
181
178
|
if not quiet:
|
|
182
179
|
rprint(f"[bold red]An unexpected error occurred:[/bold red] {str(e)}")
|
|
183
|
-
# Consider logging the full traceback here for debugging
|
|
184
|
-
# import traceback
|
|
185
|
-
# traceback.print_exc()
|
|
186
180
|
sys.exit(1)
|
pdd/data/llm_model.csv
CHANGED
|
@@ -6,7 +6,7 @@ OpenAI,deepseek/deepseek-chat,.27,1.1,1353,https://api.deepseek.com/beta,DEEPSEE
|
|
|
6
6
|
Google,vertex_ai/gemini-2.5-flash-preview-04-17,0.15,0.6,1330,,VERTEX_CREDENTIALS,0,True,effort
|
|
7
7
|
Google,gemini-2.5-pro-exp-03-25,1.25,10.0,1360,,GOOGLE_API_KEY,0,True,none
|
|
8
8
|
Anthropic,claude-sonnet-4-20250514,3.0,15.0,1340,,ANTHROPIC_API_KEY,64000,True,budget
|
|
9
|
-
Google,vertex_ai/gemini-2.5-pro
|
|
9
|
+
Google,vertex_ai/gemini-2.5-pro,1.25,10.0,1361,,VERTEX_CREDENTIALS,0,True,none
|
|
10
10
|
OpenAI,o4-mini,1.1,4.4,1333,,OPENAI_API_KEY,0,True,effort
|
|
11
11
|
OpenAI,o3,10.0,40.0,1389,,OPENAI_API_KEY,0,True,effort
|
|
12
12
|
OpenAI,gpt-4.1,2.0,8.0,1335,,OPENAI_API_KEY,0,True,none
|
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),
|
pdd/fix_code_module_errors.py
CHANGED
|
@@ -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("
|
|
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
|
))
|
pdd/fix_verification_errors.py
CHANGED
|
@@ -268,6 +268,13 @@ def fix_verification_errors(
|
|
|
268
268
|
fixed_program = fix_result_obj.fixed_program
|
|
269
269
|
fixed_code = fix_result_obj.fixed_code
|
|
270
270
|
fix_explanation = fix_result_obj.explanation
|
|
271
|
+
|
|
272
|
+
# Unescape literal \n strings to actual newlines
|
|
273
|
+
if fixed_program:
|
|
274
|
+
fixed_program = fixed_program.replace('\\n', '\n')
|
|
275
|
+
if fixed_code:
|
|
276
|
+
fixed_code = fixed_code.replace('\\n', '\n')
|
|
277
|
+
|
|
271
278
|
parsed_fix_successfully = True
|
|
272
279
|
if verbose:
|
|
273
280
|
rprint("[green]Successfully parsed structured output for fix.[/green]")
|
|
@@ -282,6 +289,12 @@ def fix_verification_errors(
|
|
|
282
289
|
fixed_code_candidate = code_match.group(1).strip() if (code_match and code_match.group(1)) else None
|
|
283
290
|
fix_explanation_candidate = explanation_match.group(1).strip() if (explanation_match and explanation_match.group(1)) else None
|
|
284
291
|
|
|
292
|
+
# Unescape literal \n strings to actual newlines
|
|
293
|
+
if fixed_program_candidate:
|
|
294
|
+
fixed_program_candidate = fixed_program_candidate.replace('\\n', '\n')
|
|
295
|
+
if fixed_code_candidate:
|
|
296
|
+
fixed_code_candidate = fixed_code_candidate.replace('\\n', '\n')
|
|
297
|
+
|
|
285
298
|
fixed_program = fixed_program_candidate if fixed_program_candidate else program
|
|
286
299
|
fixed_code = fixed_code_candidate if fixed_code_candidate else code
|
|
287
300
|
fix_explanation = fix_explanation_candidate if fix_explanation_candidate else "[Fix explanation not provided by LLM]"
|
pdd/fix_verification_main.py
CHANGED
|
@@ -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,
|
|
@@ -382,7 +382,7 @@ def fix_verification_main(
|
|
|
382
382
|
if final_code is not None:
|
|
383
383
|
rich_print(f" len(final_code): {len(final_code)}")
|
|
384
384
|
|
|
385
|
-
if
|
|
385
|
+
if output_code_path and final_code is not None:
|
|
386
386
|
try:
|
|
387
387
|
if verbose:
|
|
388
388
|
rich_print(f"[cyan bold DEBUG] In fix_verification_main, ATTEMPTING to write code to: {output_code_path!r}")
|
|
@@ -402,7 +402,7 @@ def fix_verification_main(
|
|
|
402
402
|
if final_program is not None:
|
|
403
403
|
rich_print(f" len(final_program): {len(final_program)}")
|
|
404
404
|
|
|
405
|
-
if
|
|
405
|
+
if output_program_path and final_program is not None:
|
|
406
406
|
try:
|
|
407
407
|
if verbose:
|
|
408
408
|
rich_print(f"[cyan bold DEBUG] In fix_verification_main, ATTEMPTING to write program to: {output_program_path!r}")
|