pdd-cli 0.0.45__py3-none-any.whl → 0.0.90__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 +4 -4
- pdd/agentic_common.py +863 -0
- pdd/agentic_crash.py +534 -0
- pdd/agentic_fix.py +1179 -0
- pdd/agentic_langtest.py +162 -0
- pdd/agentic_update.py +370 -0
- pdd/agentic_verify.py +183 -0
- pdd/auto_deps_main.py +15 -5
- pdd/auto_include.py +63 -5
- pdd/bug_main.py +3 -2
- pdd/bug_to_unit_test.py +2 -0
- pdd/change_main.py +11 -4
- pdd/cli.py +22 -1181
- pdd/cmd_test_main.py +73 -21
- pdd/code_generator.py +58 -18
- pdd/code_generator_main.py +672 -25
- pdd/commands/__init__.py +42 -0
- pdd/commands/analysis.py +248 -0
- pdd/commands/fix.py +140 -0
- pdd/commands/generate.py +257 -0
- pdd/commands/maintenance.py +174 -0
- pdd/commands/misc.py +79 -0
- pdd/commands/modify.py +230 -0
- pdd/commands/report.py +144 -0
- pdd/commands/templates.py +215 -0
- pdd/commands/utility.py +110 -0
- pdd/config_resolution.py +58 -0
- pdd/conflicts_main.py +8 -3
- pdd/construct_paths.py +258 -82
- pdd/context_generator.py +10 -2
- pdd/context_generator_main.py +113 -11
- pdd/continue_generation.py +47 -7
- pdd/core/__init__.py +0 -0
- pdd/core/cli.py +503 -0
- pdd/core/dump.py +554 -0
- pdd/core/errors.py +63 -0
- pdd/core/utils.py +90 -0
- pdd/crash_main.py +44 -11
- pdd/data/language_format.csv +71 -63
- pdd/data/llm_model.csv +20 -18
- pdd/detect_change_main.py +5 -4
- pdd/fix_code_loop.py +330 -76
- pdd/fix_error_loop.py +207 -61
- pdd/fix_errors_from_unit_tests.py +4 -3
- pdd/fix_main.py +75 -18
- pdd/fix_verification_errors.py +12 -100
- pdd/fix_verification_errors_loop.py +306 -272
- pdd/fix_verification_main.py +28 -9
- pdd/generate_output_paths.py +93 -10
- pdd/generate_test.py +16 -5
- pdd/get_jwt_token.py +9 -2
- pdd/get_run_command.py +73 -0
- pdd/get_test_command.py +68 -0
- pdd/git_update.py +70 -19
- pdd/incremental_code_generator.py +2 -2
- pdd/insert_includes.py +11 -3
- pdd/llm_invoke.py +1269 -103
- pdd/load_prompt_template.py +36 -10
- pdd/pdd_completion.fish +25 -2
- pdd/pdd_completion.sh +30 -4
- pdd/pdd_completion.zsh +79 -4
- pdd/postprocess.py +10 -3
- pdd/preprocess.py +228 -15
- pdd/preprocess_main.py +8 -5
- pdd/prompts/agentic_crash_explore_LLM.prompt +49 -0
- pdd/prompts/agentic_fix_explore_LLM.prompt +45 -0
- pdd/prompts/agentic_fix_harvest_only_LLM.prompt +48 -0
- pdd/prompts/agentic_fix_primary_LLM.prompt +85 -0
- pdd/prompts/agentic_update_LLM.prompt +1071 -0
- pdd/prompts/agentic_verify_explore_LLM.prompt +45 -0
- pdd/prompts/auto_include_LLM.prompt +100 -905
- pdd/prompts/detect_change_LLM.prompt +122 -20
- pdd/prompts/example_generator_LLM.prompt +22 -1
- pdd/prompts/extract_code_LLM.prompt +5 -1
- pdd/prompts/extract_program_code_fix_LLM.prompt +7 -1
- pdd/prompts/extract_prompt_update_LLM.prompt +7 -8
- pdd/prompts/extract_promptline_LLM.prompt +17 -11
- pdd/prompts/find_verification_errors_LLM.prompt +6 -0
- pdd/prompts/fix_code_module_errors_LLM.prompt +4 -2
- pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +8 -0
- pdd/prompts/fix_verification_errors_LLM.prompt +22 -0
- pdd/prompts/generate_test_LLM.prompt +21 -6
- pdd/prompts/increase_tests_LLM.prompt +1 -5
- pdd/prompts/insert_includes_LLM.prompt +228 -108
- pdd/prompts/trace_LLM.prompt +25 -22
- pdd/prompts/unfinished_prompt_LLM.prompt +85 -1
- pdd/prompts/update_prompt_LLM.prompt +22 -1
- pdd/pytest_output.py +127 -12
- pdd/render_mermaid.py +236 -0
- pdd/setup_tool.py +648 -0
- pdd/simple_math.py +2 -0
- pdd/split_main.py +3 -2
- pdd/summarize_directory.py +49 -6
- pdd/sync_determine_operation.py +543 -98
- pdd/sync_main.py +81 -31
- pdd/sync_orchestration.py +1334 -751
- pdd/sync_tui.py +848 -0
- pdd/template_registry.py +264 -0
- pdd/templates/architecture/architecture_json.prompt +242 -0
- pdd/templates/generic/generate_prompt.prompt +174 -0
- pdd/trace.py +168 -12
- pdd/trace_main.py +4 -3
- pdd/track_cost.py +151 -61
- pdd/unfinished_prompt.py +49 -3
- pdd/update_main.py +549 -67
- pdd/update_model_costs.py +2 -2
- pdd/update_prompt.py +19 -4
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.90.dist-info}/METADATA +19 -6
- pdd_cli-0.0.90.dist-info/RECORD +153 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.90.dist-info}/licenses/LICENSE +1 -1
- pdd_cli-0.0.45.dist-info/RECORD +0 -116
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.90.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.90.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.90.dist-info}/top_level.txt +0 -0
pdd/context_generator_main.py
CHANGED
|
@@ -1,11 +1,66 @@
|
|
|
1
1
|
import sys
|
|
2
|
+
import ast
|
|
2
3
|
from typing import Tuple, Optional
|
|
4
|
+
from pathlib import Path
|
|
3
5
|
import click
|
|
4
6
|
from rich import print as rprint
|
|
5
7
|
|
|
6
8
|
from .construct_paths import construct_paths
|
|
7
9
|
from .context_generator import context_generator
|
|
8
10
|
|
|
11
|
+
|
|
12
|
+
def _validate_python_syntax(code: str) -> Tuple[bool, Optional[str]]:
|
|
13
|
+
"""
|
|
14
|
+
Validate that the code is valid Python syntax.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Tuple of (is_valid, error_message)
|
|
18
|
+
"""
|
|
19
|
+
try:
|
|
20
|
+
ast.parse(code)
|
|
21
|
+
return True, None
|
|
22
|
+
except SyntaxError as e:
|
|
23
|
+
return False, f"SyntaxError at line {e.lineno}: {e.msg}"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _try_fix_json_garbage(code: str) -> Optional[str]:
|
|
27
|
+
"""
|
|
28
|
+
Attempt to fix code that has JSON metadata garbage appended at the end.
|
|
29
|
+
This is a common LLM extraction failure pattern.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Fixed code if successful, None if not fixable.
|
|
33
|
+
"""
|
|
34
|
+
lines = code.split('\n')
|
|
35
|
+
|
|
36
|
+
# Look for JSON-like patterns at the end
|
|
37
|
+
json_patterns = ['"explanation":', '"focus":', '"description":', '"code":']
|
|
38
|
+
|
|
39
|
+
for i in range(len(lines) - 1, -1, -1):
|
|
40
|
+
line = lines[i].strip()
|
|
41
|
+
|
|
42
|
+
# If we find a line that's clearly JSON garbage
|
|
43
|
+
if any(pattern in line for pattern in json_patterns):
|
|
44
|
+
# Go back and find where valid Python ends
|
|
45
|
+
for j in range(i - 1, -1, -1):
|
|
46
|
+
candidate = '\n'.join(lines[:j + 1])
|
|
47
|
+
is_valid, _ = _validate_python_syntax(candidate)
|
|
48
|
+
if is_valid:
|
|
49
|
+
return candidate
|
|
50
|
+
break
|
|
51
|
+
|
|
52
|
+
# If line is just "}" or "]", likely JSON ending
|
|
53
|
+
if line in ['}', ']', '},', '],']:
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
# If line ends with '", (JSON string end)
|
|
57
|
+
if line.endswith('",') or line.endswith('"'):
|
|
58
|
+
# Check if this looks like inside a JSON object
|
|
59
|
+
if any(pattern in lines[i] if i < len(lines) else '' for pattern in json_patterns):
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
return None
|
|
63
|
+
|
|
9
64
|
def context_generator_main(ctx: click.Context, prompt_file: str, code_file: str, output: Optional[str]) -> Tuple[str, float, str]:
|
|
10
65
|
"""
|
|
11
66
|
Main function to generate example code from a prompt file and an existing code file.
|
|
@@ -30,13 +85,26 @@ def context_generator_main(ctx: click.Context, prompt_file: str, code_file: str,
|
|
|
30
85
|
force=ctx.obj.get('force', False),
|
|
31
86
|
quiet=ctx.obj.get('quiet', False),
|
|
32
87
|
command="example",
|
|
33
|
-
command_options=command_options
|
|
88
|
+
command_options=command_options,
|
|
89
|
+
context_override=ctx.obj.get('context'),
|
|
90
|
+
confirm_callback=ctx.obj.get('confirm_callback')
|
|
34
91
|
)
|
|
35
92
|
|
|
36
93
|
# Load input files
|
|
37
94
|
prompt_content = input_strings["prompt_file"]
|
|
38
95
|
code_content = input_strings["code_file"]
|
|
39
96
|
|
|
97
|
+
# Get resolved output path for file path information
|
|
98
|
+
resolved_output = output_file_paths["output"]
|
|
99
|
+
|
|
100
|
+
# Determine file path information for correct imports
|
|
101
|
+
from pathlib import Path
|
|
102
|
+
source_file_path = str(Path(code_file).resolve())
|
|
103
|
+
example_file_path = str(Path(resolved_output).resolve()) if resolved_output else ""
|
|
104
|
+
|
|
105
|
+
# Extract module name from the code file
|
|
106
|
+
module_name = Path(code_file).stem
|
|
107
|
+
|
|
40
108
|
# Generate example code
|
|
41
109
|
strength = ctx.obj.get('strength', 0.5)
|
|
42
110
|
temperature = ctx.obj.get('temperature', 0)
|
|
@@ -48,21 +116,51 @@ def context_generator_main(ctx: click.Context, prompt_file: str, code_file: str,
|
|
|
48
116
|
strength=strength,
|
|
49
117
|
temperature=temperature,
|
|
50
118
|
time=time,
|
|
51
|
-
verbose=ctx.obj.get('verbose', False)
|
|
119
|
+
verbose=ctx.obj.get('verbose', False),
|
|
120
|
+
source_file_path=source_file_path,
|
|
121
|
+
example_file_path=example_file_path,
|
|
122
|
+
module_name=module_name
|
|
52
123
|
)
|
|
53
124
|
|
|
54
|
-
# Save results -
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
125
|
+
# Save results - if output is a directory, use resolved file path from construct_paths
|
|
126
|
+
if output is None:
|
|
127
|
+
final_output_path = resolved_output
|
|
128
|
+
else:
|
|
129
|
+
try:
|
|
130
|
+
is_dir_hint = output.endswith('/')
|
|
131
|
+
except Exception:
|
|
132
|
+
is_dir_hint = False
|
|
133
|
+
if is_dir_hint or (Path(output).exists() and Path(output).is_dir()):
|
|
134
|
+
final_output_path = resolved_output
|
|
135
|
+
else:
|
|
136
|
+
final_output_path = output
|
|
59
137
|
if final_output_path and example_code is not None:
|
|
138
|
+
# Validate Python syntax before saving
|
|
139
|
+
if language == "python":
|
|
140
|
+
is_valid, error_msg = _validate_python_syntax(example_code)
|
|
141
|
+
if not is_valid:
|
|
142
|
+
if not ctx.obj.get('quiet', False):
|
|
143
|
+
rprint(f"[yellow]Warning: Generated code has syntax error: {error_msg}[/yellow]")
|
|
144
|
+
rprint("[yellow]Attempting to fix JSON garbage pattern...[/yellow]")
|
|
145
|
+
|
|
146
|
+
# Try to fix JSON garbage at end of file
|
|
147
|
+
fixed_code = _try_fix_json_garbage(example_code)
|
|
148
|
+
if fixed_code:
|
|
149
|
+
is_valid_fixed, _ = _validate_python_syntax(fixed_code)
|
|
150
|
+
if is_valid_fixed:
|
|
151
|
+
if not ctx.obj.get('quiet', False):
|
|
152
|
+
rprint("[green]Successfully removed garbage and fixed syntax.[/green]")
|
|
153
|
+
example_code = fixed_code
|
|
154
|
+
else:
|
|
155
|
+
rprint("[red]Could not fix syntax error. Saving as-is.[/red]")
|
|
156
|
+
else:
|
|
157
|
+
rprint("[red]Could not detect fixable pattern. Saving as-is.[/red]")
|
|
158
|
+
|
|
60
159
|
with open(final_output_path, 'w') as f:
|
|
61
160
|
f.write(example_code)
|
|
62
161
|
elif final_output_path and example_code is None:
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
rprint("[bold red]Warning:[/bold red] Example generation failed, skipping file write")
|
|
162
|
+
# Raise error instead of just warning
|
|
163
|
+
raise click.UsageError("Example generation failed, no code produced.")
|
|
66
164
|
|
|
67
165
|
# Provide user feedback
|
|
68
166
|
if not ctx.obj.get('quiet', False):
|
|
@@ -85,7 +183,11 @@ def context_generator_main(ctx: click.Context, prompt_file: str, code_file: str,
|
|
|
85
183
|
|
|
86
184
|
return example_code, total_cost, model_name
|
|
87
185
|
|
|
186
|
+
except click.Abort:
|
|
187
|
+
# User cancelled - re-raise to stop the sync loop
|
|
188
|
+
raise
|
|
88
189
|
except Exception as e:
|
|
89
190
|
if not ctx.obj.get('quiet', False):
|
|
90
191
|
rprint(f"[bold red]Error:[/bold red] {str(e)}")
|
|
91
|
-
sys.exit(1)
|
|
192
|
+
# Return error result instead of sys.exit(1) to allow orchestrator to handle gracefully
|
|
193
|
+
return "", 0.0, f"Error: {e}"
|
pdd/continue_generation.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from typing import Tuple
|
|
1
|
+
from typing import Tuple, Optional
|
|
2
|
+
import logging
|
|
2
3
|
from rich.console import Console
|
|
3
4
|
from rich.syntax import Syntax
|
|
4
5
|
from pydantic import BaseModel, Field
|
|
@@ -9,6 +10,10 @@ from .unfinished_prompt import unfinished_prompt
|
|
|
9
10
|
from . import EXTRACTION_STRENGTH, DEFAULT_TIME
|
|
10
11
|
|
|
11
12
|
console = Console()
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
# Maximum number of generation loops to prevent infinite loops
|
|
16
|
+
MAX_GENERATION_LOOPS = 20
|
|
12
17
|
|
|
13
18
|
class TrimResultsStartOutput(BaseModel):
|
|
14
19
|
explanation: str = Field(description="The explanation of how you determined what to cut out")
|
|
@@ -24,6 +29,7 @@ def continue_generation(
|
|
|
24
29
|
strength: float,
|
|
25
30
|
temperature: float,
|
|
26
31
|
time: float = DEFAULT_TIME,
|
|
32
|
+
language: Optional[str] = None,
|
|
27
33
|
verbose: bool = False
|
|
28
34
|
) -> Tuple[str, float, str]:
|
|
29
35
|
"""
|
|
@@ -84,10 +90,16 @@ def continue_generation(
|
|
|
84
90
|
code_block = trim_start_response['result'].code_block
|
|
85
91
|
|
|
86
92
|
# Step 4: Continue generation loop
|
|
87
|
-
while
|
|
93
|
+
while loop_count < MAX_GENERATION_LOOPS:
|
|
88
94
|
loop_count += 1
|
|
89
95
|
if verbose:
|
|
90
96
|
console.print(f"[cyan]Generation loop {loop_count}[/cyan]")
|
|
97
|
+
|
|
98
|
+
# Check for maximum loops reached
|
|
99
|
+
if loop_count >= MAX_GENERATION_LOOPS:
|
|
100
|
+
logger.warning(f"Reached maximum generation loops ({MAX_GENERATION_LOOPS}), terminating")
|
|
101
|
+
console.print(f"[yellow]Warning: Reached maximum generation loops ({MAX_GENERATION_LOOPS}), terminating[/yellow]")
|
|
102
|
+
break
|
|
91
103
|
|
|
92
104
|
# Generate continuation
|
|
93
105
|
continue_response = llm_invoke(
|
|
@@ -106,19 +118,47 @@ def continue_generation(
|
|
|
106
118
|
model_name = continue_response['model_name']
|
|
107
119
|
continue_result = continue_response['result']
|
|
108
120
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
121
|
+
if verbose:
|
|
122
|
+
try:
|
|
123
|
+
preview = (continue_result[:160] + '...') if isinstance(continue_result, str) and len(continue_result) > 160 else continue_result
|
|
124
|
+
except Exception:
|
|
125
|
+
preview = "<non-str>"
|
|
126
|
+
console.print(f"[blue]Continue model:[/blue] {model_name}")
|
|
127
|
+
console.print(f"[blue]Continue preview:[/blue] {preview!r}")
|
|
128
|
+
|
|
129
|
+
# If the model produced no continuation, avoid an endless loop
|
|
130
|
+
if not isinstance(continue_result, str) or not continue_result.strip():
|
|
131
|
+
logger.warning("Empty continuation received; stopping to avoid loop.")
|
|
132
|
+
break
|
|
133
|
+
|
|
134
|
+
# Build prospective new block and check completeness on the updated tail
|
|
135
|
+
new_code_block = code_block + continue_result
|
|
136
|
+
last_chunk = new_code_block[-600:] if len(new_code_block) > 600 else new_code_block
|
|
137
|
+
reasoning, is_finished, check_cost, check_model = unfinished_prompt(
|
|
112
138
|
prompt_text=last_chunk,
|
|
113
139
|
strength=0.5,
|
|
114
140
|
temperature=0,
|
|
115
141
|
time=time,
|
|
142
|
+
language=language,
|
|
116
143
|
verbose=verbose
|
|
117
144
|
)
|
|
118
145
|
total_cost += check_cost
|
|
119
146
|
|
|
147
|
+
if verbose:
|
|
148
|
+
console.print(f"[magenta]Tail length:[/magenta] {len(last_chunk)}")
|
|
149
|
+
# Show a safe, shortened representation of the tail
|
|
150
|
+
try:
|
|
151
|
+
tail_preview = (last_chunk[-200:] if len(last_chunk) > 200 else last_chunk)
|
|
152
|
+
except Exception:
|
|
153
|
+
tail_preview = "<unprintable tail>"
|
|
154
|
+
console.print(f"[magenta]Tail preview (last 200 chars):[/magenta]\n{tail_preview}")
|
|
155
|
+
console.print(f"[magenta]Unfinished check model:[/magenta] {check_model}")
|
|
156
|
+
console.print(f"[magenta]is_finished:[/magenta] {is_finished}")
|
|
157
|
+
console.print(f"[magenta]Reasoning:[/magenta] {reasoning}")
|
|
158
|
+
|
|
120
159
|
if not is_finished:
|
|
121
|
-
code_block
|
|
160
|
+
code_block = new_code_block
|
|
161
|
+
# Continue to next iteration
|
|
122
162
|
else:
|
|
123
163
|
# Trim and append final continuation
|
|
124
164
|
trim_response = llm_invoke(
|
|
@@ -146,4 +186,4 @@ def continue_generation(
|
|
|
146
186
|
|
|
147
187
|
except Exception as e:
|
|
148
188
|
console.print(f"[bold red]Error in continue_generation: {str(e)}[/bold red]")
|
|
149
|
-
raise
|
|
189
|
+
raise
|
pdd/core/__init__.py
ADDED
|
File without changes
|