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/cmd_test_main.py
CHANGED
|
@@ -3,9 +3,11 @@ Main entry point for the 'test' command.
|
|
|
3
3
|
"""
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
import click
|
|
6
|
+
from pathlib import Path
|
|
6
7
|
# pylint: disable=redefined-builtin
|
|
7
8
|
from rich import print
|
|
8
9
|
|
|
10
|
+
from .config_resolution import resolve_effective_config
|
|
9
11
|
from .construct_paths import construct_paths
|
|
10
12
|
from .generate_test import generate_test
|
|
11
13
|
from .increase_tests import increase_tests
|
|
@@ -19,9 +21,11 @@ def cmd_test_main(
|
|
|
19
21
|
output: str | None,
|
|
20
22
|
language: str | None,
|
|
21
23
|
coverage_report: str | None,
|
|
22
|
-
existing_tests: str | None,
|
|
24
|
+
existing_tests: list[str] | None,
|
|
23
25
|
target_coverage: float | None,
|
|
24
26
|
merge: bool | None,
|
|
27
|
+
strength: float | None = None,
|
|
28
|
+
temperature: float | None = None,
|
|
25
29
|
) -> tuple[str, float, str]:
|
|
26
30
|
"""
|
|
27
31
|
CLI wrapper for generating or enhancing unit tests.
|
|
@@ -36,7 +40,7 @@ def cmd_test_main(
|
|
|
36
40
|
output (str | None): Path to save the generated test file.
|
|
37
41
|
language (str | None): Programming language.
|
|
38
42
|
coverage_report (str | None): Path to the coverage report file.
|
|
39
|
-
existing_tests (str | None):
|
|
43
|
+
existing_tests (list[str] | None): Paths to the existing unit test files.
|
|
40
44
|
target_coverage (float | None): Desired code coverage percentage.
|
|
41
45
|
merge (bool | None): Whether to merge new tests with existing tests.
|
|
42
46
|
|
|
@@ -51,9 +55,9 @@ def cmd_test_main(
|
|
|
51
55
|
input_strings = {}
|
|
52
56
|
|
|
53
57
|
verbose = ctx.obj["verbose"]
|
|
54
|
-
strength
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
# Note: strength/temperature will be resolved after construct_paths using resolve_effective_config
|
|
59
|
+
param_strength = strength # Store the parameter value for later resolution
|
|
60
|
+
param_temperature = temperature # Store the parameter value for later resolution
|
|
57
61
|
|
|
58
62
|
if verbose:
|
|
59
63
|
print(f"[bold blue]Prompt file:[/bold blue] {prompt_file}")
|
|
@@ -72,7 +76,7 @@ def cmd_test_main(
|
|
|
72
76
|
if coverage_report:
|
|
73
77
|
input_file_paths["coverage_report"] = coverage_report
|
|
74
78
|
if existing_tests:
|
|
75
|
-
input_file_paths["existing_tests"] = existing_tests
|
|
79
|
+
input_file_paths["existing_tests"] = existing_tests[0]
|
|
76
80
|
|
|
77
81
|
command_options = {
|
|
78
82
|
"output": output,
|
|
@@ -87,18 +91,58 @@ def cmd_test_main(
|
|
|
87
91
|
quiet=ctx.obj["quiet"],
|
|
88
92
|
command="test",
|
|
89
93
|
command_options=command_options,
|
|
94
|
+
context_override=ctx.obj.get('context'),
|
|
95
|
+
confirm_callback=ctx.obj.get('confirm_callback')
|
|
90
96
|
)
|
|
97
|
+
|
|
98
|
+
# Read multiple existing test files and concatenate their content
|
|
99
|
+
if existing_tests:
|
|
100
|
+
existing_tests_content = ""
|
|
101
|
+
for test_file in existing_tests:
|
|
102
|
+
with open(test_file, 'r') as f:
|
|
103
|
+
existing_tests_content += f.read() + "\n"
|
|
104
|
+
input_strings["existing_tests"] = existing_tests_content
|
|
105
|
+
|
|
106
|
+
# Use centralized config resolution with proper priority:
|
|
107
|
+
# CLI > pddrc > defaults
|
|
108
|
+
effective_config = resolve_effective_config(
|
|
109
|
+
ctx,
|
|
110
|
+
resolved_config,
|
|
111
|
+
param_overrides={"strength": param_strength, "temperature": param_temperature}
|
|
112
|
+
)
|
|
113
|
+
strength = effective_config["strength"]
|
|
114
|
+
temperature = effective_config["temperature"]
|
|
115
|
+
time = effective_config["time"]
|
|
116
|
+
except click.Abort:
|
|
117
|
+
# User cancelled - re-raise to stop the sync loop
|
|
118
|
+
raise
|
|
91
119
|
except Exception as exception:
|
|
92
120
|
# Catching a general exception is necessary here to handle a wide range of
|
|
93
121
|
# potential errors during file I/O and path construction, ensuring the
|
|
94
122
|
# CLI remains robust.
|
|
95
123
|
print(f"[bold red]Error constructing paths: {exception}[/bold red]")
|
|
96
|
-
ctx.exit(1)
|
|
97
|
-
return "", 0.0, ""
|
|
124
|
+
# Return error result instead of ctx.exit(1) to allow orchestrator to handle gracefully
|
|
125
|
+
return "", 0.0, f"Error: {exception}"
|
|
98
126
|
|
|
99
127
|
if verbose:
|
|
100
128
|
print(f"[bold blue]Language detected:[/bold blue] {language}")
|
|
101
129
|
|
|
130
|
+
# Determine where the generated tests will be written so we can share it with the LLM
|
|
131
|
+
# Always use resolved_output since construct_paths handles numbering for test/bug commands
|
|
132
|
+
resolved_output = output_file_paths["output"]
|
|
133
|
+
output_file = resolved_output
|
|
134
|
+
if merge and existing_tests:
|
|
135
|
+
output_file = existing_tests[0]
|
|
136
|
+
|
|
137
|
+
if not output_file:
|
|
138
|
+
print("[bold red]Error: Output file path could not be determined.[/bold red]")
|
|
139
|
+
# Return error result instead of ctx.exit(1) to allow orchestrator to handle gracefully
|
|
140
|
+
return "", 0.0, "Error: Output file path could not be determined"
|
|
141
|
+
|
|
142
|
+
source_file_path_for_prompt = str(Path(code_file).expanduser().resolve())
|
|
143
|
+
test_file_path_for_prompt = str(Path(output_file).expanduser().resolve())
|
|
144
|
+
module_name_for_prompt = Path(source_file_path_for_prompt).stem if source_file_path_for_prompt else ""
|
|
145
|
+
|
|
102
146
|
# Generate or enhance unit tests
|
|
103
147
|
if not coverage_report:
|
|
104
148
|
try:
|
|
@@ -110,22 +154,25 @@ def cmd_test_main(
|
|
|
110
154
|
time=time,
|
|
111
155
|
language=language,
|
|
112
156
|
verbose=verbose,
|
|
157
|
+
source_file_path=source_file_path_for_prompt,
|
|
158
|
+
test_file_path=test_file_path_for_prompt,
|
|
159
|
+
module_name=module_name_for_prompt,
|
|
113
160
|
)
|
|
114
161
|
except Exception as exception:
|
|
115
162
|
# A general exception is caught to handle various errors that can occur
|
|
116
163
|
# during the test generation process, which involves external model
|
|
117
164
|
# interactions and complex logic.
|
|
118
165
|
print(f"[bold red]Error generating tests: {exception}[/bold red]")
|
|
119
|
-
ctx.exit(1)
|
|
120
|
-
return "", 0.0, ""
|
|
166
|
+
# Return error result instead of ctx.exit(1) to allow orchestrator to handle gracefully
|
|
167
|
+
return "", 0.0, f"Error: {exception}"
|
|
121
168
|
else:
|
|
122
169
|
if not existing_tests:
|
|
123
170
|
print(
|
|
124
171
|
"[bold red]Error: --existing-tests is required "
|
|
125
172
|
"when using --coverage-report[/bold red]"
|
|
126
173
|
)
|
|
127
|
-
ctx.exit(1)
|
|
128
|
-
return "", 0.0, ""
|
|
174
|
+
# Return error result instead of ctx.exit(1) to allow orchestrator to handle gracefully
|
|
175
|
+
return "", 0.0, "Error: --existing-tests is required when using --coverage-report"
|
|
129
176
|
try:
|
|
130
177
|
unit_test, total_cost, model_name = increase_tests(
|
|
131
178
|
existing_unit_tests=input_strings["existing_tests"],
|
|
@@ -143,13 +190,14 @@ def cmd_test_main(
|
|
|
143
190
|
# while increasing test coverage, including problems with parsing
|
|
144
191
|
# reports or interacting with the language model.
|
|
145
192
|
print(f"[bold red]Error increasing test coverage: {exception}[/bold red]")
|
|
146
|
-
ctx.exit(1)
|
|
147
|
-
return "", 0.0, ""
|
|
193
|
+
# Return error result instead of ctx.exit(1) to allow orchestrator to handle gracefully
|
|
194
|
+
return "", 0.0, f"Error: {exception}"
|
|
148
195
|
|
|
149
|
-
# Handle output -
|
|
150
|
-
|
|
196
|
+
# Handle output - always use resolved file path since construct_paths handles numbering
|
|
197
|
+
resolved_output = output_file_paths["output"]
|
|
198
|
+
output_file = resolved_output
|
|
151
199
|
if merge and existing_tests:
|
|
152
|
-
output_file = existing_tests
|
|
200
|
+
output_file = existing_tests[0] if existing_tests else None
|
|
153
201
|
|
|
154
202
|
if not output_file:
|
|
155
203
|
print("[bold red]Error: Output file path could not be determined.[/bold red]")
|
|
@@ -161,10 +209,14 @@ def cmd_test_main(
|
|
|
161
209
|
print(f"[bold red]Error: Generated unit test content is empty or whitespace-only.[/bold red]")
|
|
162
210
|
print(f"[bold yellow]Debug: unit_test length: {len(unit_test) if unit_test else 0}[/bold yellow]")
|
|
163
211
|
print(f"[bold yellow]Debug: unit_test content preview: {repr(unit_test[:100]) if unit_test else 'None'}[/bold yellow]")
|
|
164
|
-
ctx.exit(1)
|
|
165
|
-
return "", 0.0, ""
|
|
212
|
+
# Return error result instead of ctx.exit(1) to allow orchestrator to handle gracefully
|
|
213
|
+
return "", 0.0, "Error: Generated unit test content is empty"
|
|
166
214
|
|
|
167
215
|
try:
|
|
216
|
+
# Ensure parent directory exists
|
|
217
|
+
output_path = Path(output_file)
|
|
218
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
219
|
+
|
|
168
220
|
with open(output_file, "w", encoding="utf-8") as file_handle:
|
|
169
221
|
file_handle.write(unit_test)
|
|
170
222
|
print(f"[bold green]Unit tests saved to:[/bold green] {output_file}")
|
|
@@ -173,8 +225,8 @@ def cmd_test_main(
|
|
|
173
225
|
# (e.g., permissions, disk space) that can occur when writing the
|
|
174
226
|
# output file, preventing the program from crashing unexpectedly.
|
|
175
227
|
print(f"[bold red]Error saving tests to file: {exception}[/bold red]")
|
|
176
|
-
ctx.exit(1)
|
|
177
|
-
return "", 0.0, ""
|
|
228
|
+
# Return error result instead of ctx.exit(1) to allow orchestrator to handle gracefully
|
|
229
|
+
return "", 0.0, f"Error: {exception}"
|
|
178
230
|
|
|
179
231
|
if verbose:
|
|
180
232
|
print(f"[bold blue]Total cost:[/bold blue] ${total_cost:.6f}")
|
pdd/code_generator.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import re
|
|
1
3
|
from typing import Tuple, Optional
|
|
2
4
|
from rich.console import Console
|
|
3
5
|
from . import EXTRACTION_STRENGTH
|
|
@@ -17,6 +19,7 @@ def code_generator(
|
|
|
17
19
|
time: Optional[float] = None,
|
|
18
20
|
verbose: bool = False,
|
|
19
21
|
preprocess_prompt: bool = True,
|
|
22
|
+
output_schema: Optional[dict] = None,
|
|
20
23
|
) -> Tuple[str, float, str]:
|
|
21
24
|
"""
|
|
22
25
|
Generate code from a prompt using a language model.
|
|
@@ -28,6 +31,8 @@ def code_generator(
|
|
|
28
31
|
temperature (float, optional): The temperature for the LLM model. Defaults to 0.0
|
|
29
32
|
time (Optional[float], optional): The time for the LLM model. Defaults to None
|
|
30
33
|
verbose (bool, optional): Whether to print detailed information. Defaults to False
|
|
34
|
+
preprocess_prompt (bool, optional): Whether to preprocess the prompt. Defaults to True
|
|
35
|
+
output_schema (Optional[dict], optional): JSON schema to enforce structured output. Defaults to None
|
|
31
36
|
|
|
32
37
|
Returns:
|
|
33
38
|
Tuple[str, float, str]: Tuple containing (runnable_code, total_cost, model_name)
|
|
@@ -62,14 +67,37 @@ def code_generator(
|
|
|
62
67
|
# Step 2: Generate initial response
|
|
63
68
|
if verbose:
|
|
64
69
|
console.print("[bold blue]Step 2: Generating initial response[/bold blue]")
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
|
|
71
|
+
if 'data:image' in processed_prompt:
|
|
72
|
+
parts = re.split(r'(data:image/[^;]+;base64,[A-Za-z0-9+/=]+)', processed_prompt)
|
|
73
|
+
|
|
74
|
+
content = []
|
|
75
|
+
for part in parts:
|
|
76
|
+
if part.startswith('data:image'):
|
|
77
|
+
content.append({"type": "image_url", "image_url": {"url": part}})
|
|
78
|
+
elif part != "":
|
|
79
|
+
content.append({"type": "text", "text": part})
|
|
80
|
+
|
|
81
|
+
messages = [{"role": "user", "content": content}]
|
|
82
|
+
|
|
83
|
+
response = llm_invoke(
|
|
84
|
+
messages=messages,
|
|
85
|
+
strength=strength,
|
|
86
|
+
temperature=temperature,
|
|
87
|
+
time=time,
|
|
88
|
+
verbose=verbose,
|
|
89
|
+
output_schema=output_schema
|
|
90
|
+
)
|
|
91
|
+
else:
|
|
92
|
+
response = llm_invoke(
|
|
93
|
+
prompt=processed_prompt,
|
|
94
|
+
input_json={},
|
|
95
|
+
strength=strength,
|
|
96
|
+
temperature=temperature,
|
|
97
|
+
time=time,
|
|
98
|
+
verbose=verbose,
|
|
99
|
+
output_schema=output_schema
|
|
100
|
+
)
|
|
73
101
|
initial_output = response['result']
|
|
74
102
|
total_cost += response['cost']
|
|
75
103
|
model_name = response['model_name']
|
|
@@ -83,6 +111,7 @@ def code_generator(
|
|
|
83
111
|
strength=0.5,
|
|
84
112
|
temperature=0.0,
|
|
85
113
|
time=time,
|
|
114
|
+
language=language,
|
|
86
115
|
verbose=verbose
|
|
87
116
|
)
|
|
88
117
|
total_cost += check_cost
|
|
@@ -97,6 +126,7 @@ def code_generator(
|
|
|
97
126
|
strength=strength,
|
|
98
127
|
temperature=temperature,
|
|
99
128
|
time=time,
|
|
129
|
+
language=language,
|
|
100
130
|
verbose=verbose
|
|
101
131
|
)
|
|
102
132
|
total_cost += continue_cost
|
|
@@ -107,15 +137,25 @@ def code_generator(
|
|
|
107
137
|
# Step 4: Postprocess the output
|
|
108
138
|
if verbose:
|
|
109
139
|
console.print("[bold blue]Step 4: Postprocessing output[/bold blue]")
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
140
|
+
|
|
141
|
+
# For structured JSON targets, skip extract_code to avoid losing or altering schema-constrained payloads.
|
|
142
|
+
if (isinstance(language, str) and language.strip().lower() == "json") or output_schema:
|
|
143
|
+
if isinstance(final_output, str):
|
|
144
|
+
runnable_code = final_output
|
|
145
|
+
else:
|
|
146
|
+
runnable_code = json.dumps(final_output)
|
|
147
|
+
postprocess_cost = 0.0
|
|
148
|
+
model_name_post = model_name
|
|
149
|
+
else:
|
|
150
|
+
runnable_code, postprocess_cost, model_name_post = postprocess(
|
|
151
|
+
llm_output=final_output,
|
|
152
|
+
language=language,
|
|
153
|
+
strength=EXTRACTION_STRENGTH,
|
|
154
|
+
temperature=0.0,
|
|
155
|
+
time=time,
|
|
156
|
+
verbose=verbose
|
|
157
|
+
)
|
|
158
|
+
total_cost += postprocess_cost
|
|
119
159
|
|
|
120
160
|
return runnable_code, total_cost, model_name
|
|
121
161
|
|
|
@@ -126,4 +166,4 @@ def code_generator(
|
|
|
126
166
|
except Exception as e:
|
|
127
167
|
if verbose:
|
|
128
168
|
console.print(f"[bold red]Unexpected Error: {str(e)}[/bold red]")
|
|
129
|
-
raise
|
|
169
|
+
raise
|