pdd-cli 0.0.42__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 +80 -19
- 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 +281 -81
- 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 -62
- pdd/data/llm_model.csv +20 -18
- pdd/detect_change_main.py +5 -4
- pdd/fix_code_loop.py +331 -77
- pdd/fix_error_loop.py +209 -60
- 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 +319 -272
- pdd/fix_verification_main.py +57 -17
- pdd/generate_output_paths.py +93 -10
- pdd/generate_test.py +16 -5
- pdd/get_jwt_token.py +48 -9
- pdd/get_run_command.py +73 -0
- pdd/get_test_command.py +68 -0
- pdd/git_update.py +70 -19
- pdd/increase_tests.py +7 -0
- pdd/incremental_code_generator.py +2 -2
- pdd/insert_includes.py +11 -3
- pdd/llm_invoke.py +1278 -110
- 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 +98 -101
- pdd/prompts/change_LLM.prompt +1 -3
- pdd/prompts/detect_change_LLM.prompt +562 -3
- pdd/prompts/example_generator_LLM.prompt +22 -1
- pdd/prompts/extract_code_LLM.prompt +5 -1
- pdd/prompts/extract_program_code_fix_LLM.prompt +14 -2
- 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 +16 -4
- pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +6 -41
- pdd/prompts/fix_verification_errors_LLM.prompt +22 -0
- pdd/prompts/generate_test_LLM.prompt +21 -6
- pdd/prompts/increase_tests_LLM.prompt +1 -2
- pdd/prompts/insert_includes_LLM.prompt +1181 -6
- pdd/prompts/split_LLM.prompt +1 -62
- pdd/prompts/trace_LLM.prompt +25 -22
- pdd/prompts/unfinished_prompt_LLM.prompt +85 -1
- pdd/prompts/update_prompt_LLM.prompt +22 -1
- pdd/prompts/xml_convertor_LLM.prompt +3246 -7
- pdd/pytest_output.py +188 -21
- pdd/python_env_detector.py +151 -0
- 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 +56 -7
- pdd/sync_determine_operation.py +918 -186
- pdd/sync_main.py +82 -32
- pdd/sync_orchestration.py +1456 -453
- 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.42.dist-info → pdd_cli-0.0.90.dist-info}/METADATA +20 -7
- pdd_cli-0.0.90.dist-info/RECORD +153 -0
- {pdd_cli-0.0.42.dist-info → pdd_cli-0.0.90.dist-info}/licenses/LICENSE +1 -1
- pdd_cli-0.0.42.dist-info/RECORD +0 -115
- {pdd_cli-0.0.42.dist-info → pdd_cli-0.0.90.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.42.dist-info → pdd_cli-0.0.90.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.42.dist-info → pdd_cli-0.0.90.dist-info}/top_level.txt +0 -0
pdd/preprocess.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import re
|
|
3
|
+
import base64
|
|
3
4
|
import subprocess
|
|
4
|
-
from typing import List, Optional
|
|
5
|
+
from typing import List, Optional, Tuple
|
|
5
6
|
import traceback
|
|
6
7
|
from rich.console import Console
|
|
7
8
|
from rich.panel import Panel
|
|
@@ -11,22 +12,110 @@ from rich.traceback import install
|
|
|
11
12
|
install()
|
|
12
13
|
console = Console()
|
|
13
14
|
|
|
15
|
+
# Debug/Instrumentation controls
|
|
16
|
+
_DEBUG_PREPROCESS = str(os.getenv("PDD_PREPROCESS_DEBUG", "")).lower() in ("1", "true", "yes", "on")
|
|
17
|
+
_DEBUG_OUTPUT_FILE = os.getenv("PDD_PREPROCESS_DEBUG_FILE") # Optional path to write a debug report
|
|
18
|
+
_DEBUG_EVENTS: List[str] = []
|
|
19
|
+
|
|
20
|
+
def _dbg(msg: str) -> None:
|
|
21
|
+
if _DEBUG_PREPROCESS:
|
|
22
|
+
console.print(f"[dim][PPD][preprocess][/dim] {escape(msg)}")
|
|
23
|
+
_DEBUG_EVENTS.append(msg)
|
|
24
|
+
|
|
25
|
+
def _write_debug_report() -> None:
|
|
26
|
+
if _DEBUG_PREPROCESS and _DEBUG_OUTPUT_FILE:
|
|
27
|
+
try:
|
|
28
|
+
with open(_DEBUG_OUTPUT_FILE, "w", encoding="utf-8") as fh:
|
|
29
|
+
fh.write("Preprocess Debug Report\n\n")
|
|
30
|
+
for line in _DEBUG_EVENTS:
|
|
31
|
+
fh.write(line + "\n")
|
|
32
|
+
console.print(f"[green]Debug report written to:[/green] {_DEBUG_OUTPUT_FILE}")
|
|
33
|
+
except Exception as e:
|
|
34
|
+
# Report the error so users know why the log file wasn't written
|
|
35
|
+
console.print(f"[yellow]Warning: Could not write debug report to {_DEBUG_OUTPUT_FILE}: {e}[/yellow]")
|
|
36
|
+
elif _DEBUG_PREPROCESS and not _DEBUG_OUTPUT_FILE:
|
|
37
|
+
console.print("[dim]Debug mode enabled but PDD_PREPROCESS_DEBUG_FILE not set (output shown in console only)[/dim]")
|
|
38
|
+
|
|
39
|
+
def _extract_fence_spans(text: str) -> List[Tuple[int, int]]:
|
|
40
|
+
"""Return list of (start, end) spans for fenced code blocks ```...```.
|
|
41
|
+
|
|
42
|
+
The spans are [start, end) indices in the original text.
|
|
43
|
+
"""
|
|
44
|
+
spans: List[Tuple[int, int]] = []
|
|
45
|
+
try:
|
|
46
|
+
for m in re.finditer(r"```[\w\s]*\n[\s\S]*?```", text):
|
|
47
|
+
spans.append((m.start(), m.end()))
|
|
48
|
+
except Exception:
|
|
49
|
+
pass
|
|
50
|
+
return spans
|
|
51
|
+
|
|
52
|
+
def _is_inside_any_span(idx: int, spans: List[Tuple[int, int]]) -> bool:
|
|
53
|
+
for s, e in spans:
|
|
54
|
+
if s <= idx < e:
|
|
55
|
+
return True
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
def _scan_risky_placeholders(text: str) -> Tuple[List[Tuple[int, str]], List[Tuple[int, str]]]:
|
|
59
|
+
"""Scan for risky placeholders outside code fences.
|
|
60
|
+
|
|
61
|
+
Returns two lists of (line_no, snippet):
|
|
62
|
+
- single_brace: matches like {name} not doubled and not part of {{...}}
|
|
63
|
+
- template_brace: `${...}` occurrences (which include single { ... })
|
|
64
|
+
"""
|
|
65
|
+
single_brace: List[Tuple[int, str]] = []
|
|
66
|
+
template_brace: List[Tuple[int, str]] = []
|
|
67
|
+
try:
|
|
68
|
+
fence_spans = _extract_fence_spans(text)
|
|
69
|
+
# Single-brace variable placeholders (avoid matching {{ or }})
|
|
70
|
+
for m in re.finditer(r"(?<!\{)\{([A-Za-z_][A-Za-z0-9_]*)\}(?!\})", text):
|
|
71
|
+
if not _is_inside_any_span(m.start(), fence_spans):
|
|
72
|
+
line_no = text.count("\n", 0, m.start()) + 1
|
|
73
|
+
single_brace.append((line_no, m.group(0)))
|
|
74
|
+
# JavaScript template placeholders like ${...}
|
|
75
|
+
for m in re.finditer(r"\$\{[^\}]+\}", text):
|
|
76
|
+
if not _is_inside_any_span(m.start(), fence_spans):
|
|
77
|
+
line_no = text.count("\n", 0, m.start()) + 1
|
|
78
|
+
template_brace.append((line_no, m.group(0)))
|
|
79
|
+
except Exception:
|
|
80
|
+
pass
|
|
81
|
+
return single_brace, template_brace
|
|
82
|
+
|
|
14
83
|
def preprocess(prompt: str, recursive: bool = False, double_curly_brackets: bool = True, exclude_keys: Optional[List[str]] = None) -> str:
|
|
15
84
|
try:
|
|
16
85
|
if not prompt:
|
|
17
86
|
console.print("[bold red]Error:[/bold red] Empty prompt provided")
|
|
18
87
|
return ""
|
|
88
|
+
_DEBUG_EVENTS.clear()
|
|
89
|
+
_dbg(f"Start preprocess(recursive={recursive}, double_curly={double_curly_brackets}, exclude_keys={exclude_keys})")
|
|
90
|
+
_dbg(f"Initial length: {len(prompt)} characters")
|
|
19
91
|
console.print(Panel("Starting prompt preprocessing", style="bold blue"))
|
|
20
92
|
prompt = process_backtick_includes(prompt, recursive)
|
|
93
|
+
_dbg("After backtick includes processed")
|
|
21
94
|
prompt = process_xml_tags(prompt, recursive)
|
|
95
|
+
_dbg("After XML-like tags processed")
|
|
22
96
|
if double_curly_brackets:
|
|
23
97
|
prompt = double_curly(prompt, exclude_keys)
|
|
98
|
+
_dbg("After double_curly execution")
|
|
99
|
+
# Scan for risky placeholders remaining outside code fences
|
|
100
|
+
singles, templates = _scan_risky_placeholders(prompt)
|
|
101
|
+
if singles:
|
|
102
|
+
_dbg(f"WARNING: Found {len(singles)} single-brace placeholders outside code fences (examples):")
|
|
103
|
+
for ln, frag in singles[:5]:
|
|
104
|
+
_dbg(f" line {ln}: {frag}")
|
|
105
|
+
if templates:
|
|
106
|
+
_dbg(f"INFO: Found {len(templates)} template literals ${'{...'} outside code fences (examples):")
|
|
107
|
+
for ln, frag in templates[:5]:
|
|
108
|
+
_dbg(f" line {ln}: {frag}")
|
|
24
109
|
# Don't trim whitespace that might be significant for the tests
|
|
25
110
|
console.print(Panel("Preprocessing complete", style="bold green"))
|
|
111
|
+
_dbg(f"Final length: {len(prompt)} characters")
|
|
112
|
+
_write_debug_report()
|
|
26
113
|
return prompt
|
|
27
114
|
except Exception as e:
|
|
28
115
|
console.print(f"[bold red]Error during preprocessing:[/bold red] {str(e)}")
|
|
29
116
|
console.print(Panel(traceback.format_exc(), title="Error Details", style="red"))
|
|
117
|
+
_dbg(f"Exception: {str(e)}")
|
|
118
|
+
_write_debug_report()
|
|
30
119
|
return prompt
|
|
31
120
|
|
|
32
121
|
def get_file_path(file_name: str) -> str:
|
|
@@ -45,12 +134,17 @@ def process_backtick_includes(text: str, recursive: bool) -> str:
|
|
|
45
134
|
content = file.read()
|
|
46
135
|
if recursive:
|
|
47
136
|
content = preprocess(content, recursive=True, double_curly_brackets=False)
|
|
137
|
+
_dbg(f"Included via backticks: {file_path} (len={len(content)})")
|
|
48
138
|
return f"```{content}```"
|
|
49
139
|
except FileNotFoundError:
|
|
50
140
|
console.print(f"[bold red]Warning:[/bold red] File not found: {file_path}")
|
|
51
|
-
|
|
141
|
+
_dbg(f"Missing backtick include: {file_path}")
|
|
142
|
+
# First pass (recursive=True): leave the tag so a later env expansion can resolve it
|
|
143
|
+
# Second pass (recursive=False): replace with a visible placeholder
|
|
144
|
+
return match.group(0) if recursive else f"```[File not found: {file_path}]```"
|
|
52
145
|
except Exception as e:
|
|
53
146
|
console.print(f"[bold red]Error processing include:[/bold red] {str(e)}")
|
|
147
|
+
_dbg(f"Error processing backtick include {file_path}: {e}")
|
|
54
148
|
return f"```[Error processing include: {file_path}]```"
|
|
55
149
|
prev_text = ""
|
|
56
150
|
current_text = text
|
|
@@ -62,9 +156,9 @@ def process_backtick_includes(text: str, recursive: bool) -> str:
|
|
|
62
156
|
def process_xml_tags(text: str, recursive: bool) -> str:
|
|
63
157
|
text = process_pdd_tags(text)
|
|
64
158
|
text = process_include_tags(text, recursive)
|
|
65
|
-
|
|
66
|
-
text = process_shell_tags(text)
|
|
67
|
-
text = process_web_tags(text)
|
|
159
|
+
text = process_include_many_tags(text, recursive)
|
|
160
|
+
text = process_shell_tags(text, recursive)
|
|
161
|
+
text = process_web_tags(text, recursive)
|
|
68
162
|
return text
|
|
69
163
|
|
|
70
164
|
def process_include_tags(text: str, recursive: bool) -> str:
|
|
@@ -73,17 +167,63 @@ def process_include_tags(text: str, recursive: bool) -> str:
|
|
|
73
167
|
file_path = match.group(1).strip()
|
|
74
168
|
try:
|
|
75
169
|
full_path = get_file_path(file_path)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
170
|
+
ext = os.path.splitext(file_path)[1].lower()
|
|
171
|
+
image_extensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.heic']
|
|
172
|
+
|
|
173
|
+
if ext in image_extensions:
|
|
174
|
+
console.print(f"Processing image include: [cyan]{full_path}[/cyan]")
|
|
175
|
+
from PIL import Image
|
|
176
|
+
import io
|
|
177
|
+
import pillow_heif
|
|
178
|
+
|
|
179
|
+
pillow_heif.register_heif_opener()
|
|
180
|
+
|
|
181
|
+
MAX_DIMENSION = 1024
|
|
182
|
+
with open(full_path, 'rb') as file:
|
|
183
|
+
img = Image.open(file)
|
|
184
|
+
img.load() # Force loading the image data before the file closes
|
|
185
|
+
|
|
186
|
+
if img.width > MAX_DIMENSION or img.height > MAX_DIMENSION:
|
|
187
|
+
img.thumbnail((MAX_DIMENSION, MAX_DIMENSION))
|
|
188
|
+
console.print(f"Image resized to {img.size}")
|
|
189
|
+
|
|
190
|
+
# Handle GIFs: convert to a static PNG of the first frame
|
|
191
|
+
if ext == '.gif':
|
|
192
|
+
img.seek(0)
|
|
193
|
+
img = img.convert("RGB")
|
|
194
|
+
img_format = 'PNG'
|
|
195
|
+
mime_type = 'image/png'
|
|
196
|
+
elif ext == '.heic':
|
|
197
|
+
img_format = 'JPEG'
|
|
198
|
+
mime_type = 'image/jpeg'
|
|
199
|
+
else:
|
|
200
|
+
img_format = 'JPEG' if ext in ['.jpg', '.jpeg'] else 'PNG'
|
|
201
|
+
mime_type = f'image/{img_format.lower()}'
|
|
202
|
+
|
|
203
|
+
# Save the (potentially resized and converted) image to an in-memory buffer
|
|
204
|
+
buffer = io.BytesIO()
|
|
205
|
+
img.save(buffer, format=img_format)
|
|
206
|
+
content = buffer.getvalue()
|
|
207
|
+
|
|
208
|
+
encoded_string = base64.b64encode(content).decode('utf-8')
|
|
209
|
+
return f"data:{mime_type};base64,{encoded_string}"
|
|
210
|
+
else:
|
|
211
|
+
console.print(f"Processing XML include: [cyan]{full_path}[/cyan]")
|
|
212
|
+
with open(full_path, 'r', encoding='utf-8') as file:
|
|
213
|
+
content = file.read()
|
|
214
|
+
if recursive:
|
|
215
|
+
content = preprocess(content, recursive=True, double_curly_brackets=False)
|
|
216
|
+
_dbg(f"Included via XML tag: {file_path} (len={len(content)})")
|
|
217
|
+
return content
|
|
82
218
|
except FileNotFoundError:
|
|
83
219
|
console.print(f"[bold red]Warning:[/bold red] File not found: {file_path}")
|
|
84
|
-
|
|
220
|
+
_dbg(f"Missing XML include: {file_path}")
|
|
221
|
+
# First pass (recursive=True): leave the tag so a later env expansion can resolve it
|
|
222
|
+
# Second pass (recursive=False): replace with a visible placeholder
|
|
223
|
+
return match.group(0) if recursive else f"[File not found: {file_path}]"
|
|
85
224
|
except Exception as e:
|
|
86
225
|
console.print(f"[bold red]Error processing include:[/bold red] {str(e)}")
|
|
226
|
+
_dbg(f"Error processing XML include {file_path}: {e}")
|
|
87
227
|
return f"[Error processing include: {file_path}]"
|
|
88
228
|
prev_text = ""
|
|
89
229
|
current_text = text
|
|
@@ -101,54 +241,101 @@ def process_pdd_tags(text: str) -> str:
|
|
|
101
241
|
return "This is a test "
|
|
102
242
|
return processed
|
|
103
243
|
|
|
104
|
-
def process_shell_tags(text: str) -> str:
|
|
244
|
+
def process_shell_tags(text: str, recursive: bool) -> str:
|
|
105
245
|
pattern = r'<shell>(.*?)</shell>'
|
|
106
246
|
def replace_shell(match):
|
|
107
247
|
command = match.group(1).strip()
|
|
248
|
+
if recursive:
|
|
249
|
+
# Defer execution until after env var expansion
|
|
250
|
+
return match.group(0)
|
|
108
251
|
console.print(f"Executing shell command: [cyan]{escape(command)}[/cyan]")
|
|
252
|
+
_dbg(f"Shell tag command: {command}")
|
|
109
253
|
try:
|
|
110
254
|
result = subprocess.run(command, shell=True, check=True, capture_output=True, text=True)
|
|
111
255
|
return result.stdout
|
|
112
256
|
except subprocess.CalledProcessError as e:
|
|
113
257
|
error_msg = f"Command '{command}' returned non-zero exit status {e.returncode}."
|
|
114
258
|
console.print(f"[bold red]Error:[/bold red] {error_msg}")
|
|
259
|
+
_dbg(f"Shell command error: {error_msg}")
|
|
115
260
|
return f"Error: {error_msg}"
|
|
116
261
|
except Exception as e:
|
|
117
262
|
console.print(f"[bold red]Error executing shell command:[/bold red] {str(e)}")
|
|
263
|
+
_dbg(f"Shell execution exception: {e}")
|
|
118
264
|
return f"[Shell execution error: {str(e)}]"
|
|
119
265
|
return re.sub(pattern, replace_shell, text, flags=re.DOTALL)
|
|
120
266
|
|
|
121
|
-
def process_web_tags(text: str) -> str:
|
|
267
|
+
def process_web_tags(text: str, recursive: bool) -> str:
|
|
122
268
|
pattern = r'<web>(.*?)</web>'
|
|
123
269
|
def replace_web(match):
|
|
124
270
|
url = match.group(1).strip()
|
|
271
|
+
if recursive:
|
|
272
|
+
# Defer network operations until after env var expansion
|
|
273
|
+
return match.group(0)
|
|
125
274
|
console.print(f"Scraping web content from: [cyan]{url}[/cyan]")
|
|
275
|
+
_dbg(f"Web tag URL: {url}")
|
|
126
276
|
try:
|
|
127
277
|
try:
|
|
128
278
|
from firecrawl import FirecrawlApp
|
|
129
279
|
except ImportError:
|
|
280
|
+
_dbg("firecrawl import failed; package not installed")
|
|
130
281
|
return f"[Error: firecrawl-py package not installed. Cannot scrape {url}]"
|
|
131
282
|
api_key = os.environ.get('FIRECRAWL_API_KEY')
|
|
132
283
|
if not api_key:
|
|
133
284
|
console.print("[bold yellow]Warning:[/bold yellow] FIRECRAWL_API_KEY not found in environment")
|
|
285
|
+
_dbg("FIRECRAWL_API_KEY not set")
|
|
134
286
|
return f"[Error: FIRECRAWL_API_KEY not set. Cannot scrape {url}]"
|
|
135
287
|
app = FirecrawlApp(api_key=api_key)
|
|
136
288
|
response = app.scrape_url(url, formats=['markdown'])
|
|
137
289
|
if hasattr(response, 'markdown'):
|
|
290
|
+
_dbg(f"Web scrape returned markdown (len={len(response.markdown)})")
|
|
138
291
|
return response.markdown
|
|
139
292
|
else:
|
|
140
293
|
console.print(f"[bold yellow]Warning:[/bold yellow] No markdown content returned for {url}")
|
|
294
|
+
_dbg("Web scrape returned no markdown content")
|
|
141
295
|
return f"[No content available for {url}]"
|
|
142
296
|
except Exception as e:
|
|
143
297
|
console.print(f"[bold red]Error scraping web content:[/bold red] {str(e)}")
|
|
298
|
+
_dbg(f"Web scraping exception: {e}")
|
|
144
299
|
return f"[Web scraping error: {str(e)}]"
|
|
145
300
|
return re.sub(pattern, replace_web, text, flags=re.DOTALL)
|
|
146
301
|
|
|
302
|
+
def process_include_many_tags(text: str, recursive: bool) -> str:
|
|
303
|
+
"""Process <include-many> blocks whose inner content is a comma- or newline-separated
|
|
304
|
+
list of file paths (typically provided via variables after env expansion)."""
|
|
305
|
+
pattern = r'<include-many>(.*?)</include-many>'
|
|
306
|
+
def replace_many(match):
|
|
307
|
+
inner = match.group(1)
|
|
308
|
+
if recursive:
|
|
309
|
+
# Wait for env expansion to materialize the list
|
|
310
|
+
return match.group(0)
|
|
311
|
+
# Split by newlines or commas
|
|
312
|
+
raw_items = [s.strip() for part in inner.split('\n') for s in part.split(',')]
|
|
313
|
+
paths = [p for p in raw_items if p]
|
|
314
|
+
contents: list[str] = []
|
|
315
|
+
for p in paths:
|
|
316
|
+
try:
|
|
317
|
+
full_path = get_file_path(p)
|
|
318
|
+
console.print(f"Including (many): [cyan]{full_path}[/cyan]")
|
|
319
|
+
with open(full_path, 'r', encoding='utf-8') as fh:
|
|
320
|
+
contents.append(fh.read())
|
|
321
|
+
_dbg(f"Included (many): {p}")
|
|
322
|
+
except FileNotFoundError:
|
|
323
|
+
console.print(f"[bold red]Warning:[/bold red] File not found: {p}")
|
|
324
|
+
_dbg(f"Missing include-many: {p}")
|
|
325
|
+
contents.append(f"[File not found: {p}]")
|
|
326
|
+
except Exception as e:
|
|
327
|
+
console.print(f"[bold red]Error processing include-many:[/bold red] {str(e)}")
|
|
328
|
+
_dbg(f"Error processing include-many {p}: {e}")
|
|
329
|
+
contents.append(f"[Error processing include: {p}]")
|
|
330
|
+
return "\n".join(contents)
|
|
331
|
+
return re.sub(pattern, replace_many, text, flags=re.DOTALL)
|
|
332
|
+
|
|
147
333
|
def double_curly(text: str, exclude_keys: Optional[List[str]] = None) -> str:
|
|
148
334
|
if exclude_keys is None:
|
|
149
335
|
exclude_keys = []
|
|
150
336
|
|
|
151
337
|
console.print("Doubling curly brackets...")
|
|
338
|
+
_dbg("double_curly invoked")
|
|
152
339
|
|
|
153
340
|
# Special case handling for specific test patterns
|
|
154
341
|
if "Mix of {excluded{inner}} nesting" in text and "excluded" in exclude_keys:
|
|
@@ -172,6 +359,14 @@ def double_curly(text: str, exclude_keys: Optional[List[str]] = None) -> str:
|
|
|
172
359
|
"2": {{"id": "2", "name": "Resource Two"}}
|
|
173
360
|
}}"""
|
|
174
361
|
|
|
362
|
+
# Protect ${IDENT} placeholders so we can safely double braces, then restore
|
|
363
|
+
# them as ${{IDENT}} to avoid PromptTemplate interpreting {IDENT}.
|
|
364
|
+
protected_vars: List[str] = []
|
|
365
|
+
def _protect_var(m):
|
|
366
|
+
protected_vars.append(m.group(0))
|
|
367
|
+
return f"__PDD_VAR_{len(protected_vars)-1}__"
|
|
368
|
+
text = re.sub(r"\$\{[A-Za-z_][A-Za-z0-9_]*\}", _protect_var, text)
|
|
369
|
+
|
|
175
370
|
# First, protect any existing double curly braces
|
|
176
371
|
text = re.sub(r'\{\{([^{}]*)\}\}', r'__ALREADY_DOUBLED__\1__END_ALREADY__', text)
|
|
177
372
|
|
|
@@ -188,6 +383,24 @@ def double_curly(text: str, exclude_keys: Optional[List[str]] = None) -> str:
|
|
|
188
383
|
|
|
189
384
|
# Restore already doubled brackets
|
|
190
385
|
text = re.sub(r'__ALREADY_DOUBLED__(.*?)__END_ALREADY__', r'{{\1}}', text)
|
|
386
|
+
|
|
387
|
+
# Restore protected ${IDENT} placeholders as ${{IDENT}} so single braces
|
|
388
|
+
# don't leak into PromptTemplate formatting. This is safe for JS template
|
|
389
|
+
# literals and prevents missing-key errors in later formatting steps.
|
|
390
|
+
def _restore_var(m):
|
|
391
|
+
idx = int(m.group(1))
|
|
392
|
+
if 0 <= idx < len(protected_vars):
|
|
393
|
+
original = protected_vars[idx] # e.g., ${FOO}
|
|
394
|
+
try:
|
|
395
|
+
inner = re.match(r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}", original)
|
|
396
|
+
if inner:
|
|
397
|
+
# Build as concatenation to avoid f-string brace escaping confusion
|
|
398
|
+
return "${{" + inner.group(1) + "}}" # -> ${{FOO}}
|
|
399
|
+
except Exception:
|
|
400
|
+
pass
|
|
401
|
+
return original
|
|
402
|
+
return m.group(0)
|
|
403
|
+
text = re.sub(r"__PDD_VAR_(\d+)__", _restore_var, text)
|
|
191
404
|
|
|
192
405
|
# Special handling for code blocks
|
|
193
406
|
code_block_pattern = r'```([\w\s]*)\n([\s\S]*?)```'
|
|
@@ -213,4 +426,4 @@ def double_curly(text: str, exclude_keys: Optional[List[str]] = None) -> str:
|
|
|
213
426
|
# Process code blocks
|
|
214
427
|
text = re.sub(code_block_pattern, process_code_block, text, flags=re.DOTALL)
|
|
215
428
|
|
|
216
|
-
return text
|
|
429
|
+
return text
|
pdd/preprocess_main.py
CHANGED
|
@@ -4,10 +4,10 @@ from typing import Tuple, Optional
|
|
|
4
4
|
import click
|
|
5
5
|
from rich import print as rprint
|
|
6
6
|
|
|
7
|
+
from .config_resolution import resolve_effective_config
|
|
7
8
|
from .construct_paths import construct_paths
|
|
8
9
|
from .preprocess import preprocess
|
|
9
10
|
from .xml_tagger import xml_tagger
|
|
10
|
-
from . import DEFAULT_TIME, DEFAULT_STRENGTH
|
|
11
11
|
def preprocess_main(
|
|
12
12
|
ctx: click.Context, prompt_file: str, output: Optional[str], xml: bool, recursive: bool, double: bool, exclude: list
|
|
13
13
|
) -> Tuple[str, float, str]:
|
|
@@ -33,6 +33,7 @@ def preprocess_main(
|
|
|
33
33
|
quiet=ctx.obj.get("quiet", False),
|
|
34
34
|
command="preprocess",
|
|
35
35
|
command_options=command_options,
|
|
36
|
+
context_override=ctx.obj.get('context')
|
|
36
37
|
)
|
|
37
38
|
|
|
38
39
|
# Load prompt file
|
|
@@ -40,10 +41,12 @@ def preprocess_main(
|
|
|
40
41
|
|
|
41
42
|
if xml:
|
|
42
43
|
# Use xml_tagger to add XML delimiters
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
# Use centralized config resolution with proper priority: CLI > pddrc > defaults
|
|
45
|
+
effective_config = resolve_effective_config(ctx, resolved_config)
|
|
46
|
+
strength = effective_config["strength"]
|
|
47
|
+
temperature = effective_config["temperature"]
|
|
48
|
+
time = effective_config["time"]
|
|
45
49
|
verbose = ctx.obj.get("verbose", False)
|
|
46
|
-
time = ctx.obj.get("time", DEFAULT_TIME)
|
|
47
50
|
xml_tagged, total_cost, model_name = xml_tagger(
|
|
48
51
|
prompt,
|
|
49
52
|
strength,
|
|
@@ -76,4 +79,4 @@ def preprocess_main(
|
|
|
76
79
|
except Exception as e:
|
|
77
80
|
if not ctx.obj.get("quiet", False):
|
|
78
81
|
rprint(f"[bold red]Error during preprocessing:[/bold red] {e}")
|
|
79
|
-
sys.exit(1)
|
|
82
|
+
sys.exit(1)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
You are fixing a crash in a PDD (Prompt-Driven Development) project.
|
|
2
|
+
You are running as FALLBACK after PDD's normal crash loop failed multiple times. This loop was only allowed to change the code and/or program file.
|
|
3
|
+
The error(s) is likely outside of these files.
|
|
4
|
+
|
|
5
|
+
## PDD Principle
|
|
6
|
+
The PROMPT FILE is the source of truth. Code is a generated artifact.
|
|
7
|
+
The PROGRAM FILE calls the code and crashed. Both files may need fixes.
|
|
8
|
+
|
|
9
|
+
## Files (you have full read/write access)
|
|
10
|
+
- Prompt file (THE SPEC): {prompt_path}
|
|
11
|
+
- Code file: {code_path}
|
|
12
|
+
- Program file: {program_path}
|
|
13
|
+
- Project root: {project_root}
|
|
14
|
+
|
|
15
|
+
## Previous Fix Attempts
|
|
16
|
+
The following shows what PDD's normal crash loop already tried.
|
|
17
|
+
DO NOT repeat these approaches - try something different.
|
|
18
|
+
|
|
19
|
+
<previous_attempts>
|
|
20
|
+
{previous_attempts}
|
|
21
|
+
</previous_attempts>
|
|
22
|
+
|
|
23
|
+
## Your Task
|
|
24
|
+
1. Read the prompt file to understand the intended behavior
|
|
25
|
+
2. Read the code and program files
|
|
26
|
+
3. Analyze the crash traceback to identify the root cause
|
|
27
|
+
4. Explore related files (imports, dependencies, conftest.py) if needed
|
|
28
|
+
5. Determine what needs fixing:
|
|
29
|
+
- Code has a bug -> fix the code
|
|
30
|
+
- Program calls code incorrectly -> fix the program
|
|
31
|
+
- Both have issues -> fix both
|
|
32
|
+
- Issue requires changes to other files -> make those changes
|
|
33
|
+
6. Make ALL necessary changes to stop the crash including other files if needed
|
|
34
|
+
7. Run the program file to verify the fix
|
|
35
|
+
8. Repeat steps 4-7 until the program output aligns with the prompt's intent
|
|
36
|
+
9. Output a JSON string with the following fields:
|
|
37
|
+
- success: bool
|
|
38
|
+
- message: str
|
|
39
|
+
- cost: float
|
|
40
|
+
- model: str
|
|
41
|
+
- changed_files: list[str]
|
|
42
|
+
|
|
43
|
+
## Critical Rules
|
|
44
|
+
- The prompt file defines what's correct - code should conform to it
|
|
45
|
+
- DO NOT repeat approaches from the fix history above
|
|
46
|
+
- You MAY modify BOTH the code file AND the program file
|
|
47
|
+
- IMPORTANT: Read actual source files before assuming what functions/classes exist
|
|
48
|
+
- Do NOT guess at imports or API names
|
|
49
|
+
- Explore the codebase to understand actual exports
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
You are fixing a test failure in a PDD (Prompt-Driven Development) project.
|
|
2
|
+
You are running as FALLBACK after PDD's normal fix loop failed multiple times. This loop was only allowed to change the code and/or test file.
|
|
3
|
+
|
|
4
|
+
## PDD Principle
|
|
5
|
+
The PROMPT FILE is the source of truth. Code and tests are generated artifacts.
|
|
6
|
+
If tests expect behavior not defined in the prompt, the TESTS may be wrong.
|
|
7
|
+
|
|
8
|
+
## Files (you have full read/write access)
|
|
9
|
+
- Prompt file (THE SPEC): {prompt_path}
|
|
10
|
+
- Code file: {code_path}
|
|
11
|
+
- Test file: {test_path}
|
|
12
|
+
- Example program file: {example_program_path}
|
|
13
|
+
- Project root: {project_root}
|
|
14
|
+
|
|
15
|
+
## Previous Fix Attempts
|
|
16
|
+
The following shows what PDD's normal fix loop already tried.
|
|
17
|
+
DO NOT repeat these approaches - try something different.
|
|
18
|
+
|
|
19
|
+
{error_content}
|
|
20
|
+
|
|
21
|
+
## Your Task
|
|
22
|
+
1. Read the prompt file to understand the intended behavior
|
|
23
|
+
2. Read the code and test files
|
|
24
|
+
3. Run test file to get the error(s)
|
|
25
|
+
4. Explore related files (helpers, fixtures, etc.) if needed
|
|
26
|
+
5. Determine what needs fixing:
|
|
27
|
+
- Code doesn't match the prompt spec -> fix the code
|
|
28
|
+
- Tests don't match the prompt spec -> fix the tests
|
|
29
|
+
- Tests have implementation issues (mocking, isolation) -> fix test implementation
|
|
30
|
+
- Issue requires changes to other files -> make those changes
|
|
31
|
+
5. Make ALL necessary changes to fix the tests
|
|
32
|
+
6. Run the example program file to verify the fix didn't break the program
|
|
33
|
+
7. Repeat steps 4-6 until the program output aligns with the prompt's intent
|
|
34
|
+
8. Output a JSON string with the following fields:
|
|
35
|
+
- success: bool
|
|
36
|
+
- message: str
|
|
37
|
+
- cost: float
|
|
38
|
+
- model: str
|
|
39
|
+
- changed_files: list[str]
|
|
40
|
+
|
|
41
|
+
## Critical Rules
|
|
42
|
+
- The prompt file defines what's correct - code and tests should conform to it
|
|
43
|
+
- DO NOT repeat approaches from the fix history above
|
|
44
|
+
- You may modify existing files or create new ones
|
|
45
|
+
- If the error involves mocking/test isolation, focus on the TEST implementation
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
% You are a strictly constrained code emitter. Your goal is to make {test_abs} run without errors. The bug could be in EITHER {code_abs} (the source code) OR {test_abs} (the test/example file). Analyze the error carefully to determine which file needs fixing. Read the prompt content which describes the intended functionality, then fix the appropriate file(s). Your ONLY task is to output fully corrected contents of one or more changed files, and optionally one shell command to run the tests. Wrap outputs between the provided BEGIN/END markers. No commentary or extra text.
|
|
2
|
+
|
|
3
|
+
% IMPORTANT: Analyze the error traceback carefully:
|
|
4
|
+
- If the error is in how the test/example USES the code (wrong exception caught, wrong API usage), fix {test_abs}
|
|
5
|
+
- If the error is in the code's IMPLEMENTATION (wrong behavior, missing functionality), fix {code_abs}
|
|
6
|
+
- You may need to fix BOTH files in some cases
|
|
7
|
+
|
|
8
|
+
% IMPORTANT: If you see ModuleNotFoundError or ImportError:
|
|
9
|
+
- For external packages: include "pip install <package> &&" before the test command in TESTCMD
|
|
10
|
+
- For local imports: fix the sys.path or import statement to correctly locate {code_abs}
|
|
11
|
+
- The code file is at: {code_abs} - ensure imports can find this path
|
|
12
|
+
|
|
13
|
+
<inputs>
|
|
14
|
+
<paths>
|
|
15
|
+
<begin_marker>{begin}</begin_marker>
|
|
16
|
+
<end_marker>{end}</end_marker>
|
|
17
|
+
<code_file>{code_abs}</code_file>
|
|
18
|
+
</paths>
|
|
19
|
+
|
|
20
|
+
<context>
|
|
21
|
+
<prompt_content>
|
|
22
|
+
{prompt_content}
|
|
23
|
+
</prompt_content>
|
|
24
|
+
<relevant_error>
|
|
25
|
+
{error_content}
|
|
26
|
+
</relevant_error>
|
|
27
|
+
</context>
|
|
28
|
+
</inputs>
|
|
29
|
+
|
|
30
|
+
% Follow these instructions:
|
|
31
|
+
|
|
32
|
+
1) Output ALL files you changed that are needed to make tests pass (source files, tests, or small support files).
|
|
33
|
+
Use one block per file, with this exact wrapping:
|
|
34
|
+
<<<BEGIN_FILE:{code_abs}>>>
|
|
35
|
+
<FULL CORRECTED FILE CONTENT>
|
|
36
|
+
<<<END_FILE:{code_abs}>>>
|
|
37
|
+
|
|
38
|
+
If you also modify the test file:
|
|
39
|
+
<<<BEGIN_FILE:{test_abs}>>>
|
|
40
|
+
<FULL CORRECTED FILE CONTENT>
|
|
41
|
+
<<<END_FILE:{test_abs}>>>
|
|
42
|
+
|
|
43
|
+
2) If you cannot run tests, ALSO print a single block containing the exact shell command to run tests such that it returns 0 on success:
|
|
44
|
+
<<<BEGIN_TESTCMD>>>
|
|
45
|
+
python {test_abs}
|
|
46
|
+
<<<END_TESTCMD>>>
|
|
47
|
+
|
|
48
|
+
3) Print nothing else. No code fences, no comments, no prose.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
% YOU ARE A DEBUGGING AGENT with full file system access.
|
|
2
|
+
|
|
3
|
+
% TASK: Fix the failing test at {test_abs}
|
|
4
|
+
|
|
5
|
+
% APPROACH:
|
|
6
|
+
1. Read the error traceback carefully to understand what's failing
|
|
7
|
+
2. Explore the relevant files to understand the codebase structure
|
|
8
|
+
3. Identify the root cause - is the bug in the code module or the test file or both?
|
|
9
|
+
4. Use your file editing tools to make minimal, targeted fixes
|
|
10
|
+
5. After fixing, output the test command to verify your changes
|
|
11
|
+
|
|
12
|
+
% FILES YOU CAN READ AND EDIT:
|
|
13
|
+
<code_module>
|
|
14
|
+
{code_abs}
|
|
15
|
+
</code_module>
|
|
16
|
+
<test_file>
|
|
17
|
+
{test_abs}
|
|
18
|
+
</test_file>
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
% ORIGINAL SPECIFICATION:
|
|
22
|
+
<proompt_content>
|
|
23
|
+
{prompt_content}
|
|
24
|
+
</proompt_content>
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
% ERROR LOG:
|
|
28
|
+
<error_content>
|
|
29
|
+
{error_content}
|
|
30
|
+
</error_content>
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
% DEBUGGING GUIDELINES:
|
|
34
|
+
- Analyze the traceback to find WHERE the error occurs and WHY
|
|
35
|
+
- The bug could be in EITHER file - don't assume it's always in the code
|
|
36
|
+
- If the error is in how the test USES the code → fix the test
|
|
37
|
+
- If the error is in the code's IMPLEMENTATION → fix the code
|
|
38
|
+
- You may need to fix BOTH files in some cases
|
|
39
|
+
|
|
40
|
+
% COMMON ERROR TYPES AND FIXES:
|
|
41
|
+
- ImportError/ModuleNotFoundError for LOCAL modules: The import statement may be wrong.
|
|
42
|
+
FIX: Change the import to use the correct module name (look at what modules exist).
|
|
43
|
+
DO NOT create new modules to match a wrong import - fix the import instead!
|
|
44
|
+
- ImportError/ModuleNotFoundError for EXTERNAL packages (pip packages like toml, requests, humanize, etc.):
|
|
45
|
+
PREFERRED: Install the missing package using TESTCMD:
|
|
46
|
+
<<<BEGIN_TESTCMD>>>
|
|
47
|
+
pip install <package_name> && python -m pytest "{test_abs}" -q
|
|
48
|
+
<<<END_TESTCMD>>>
|
|
49
|
+
|
|
50
|
+
DO NOT rewrite the code to remove or replace the dependency unless the specification
|
|
51
|
+
explicitly says the dependency is optional. If the code uses a library, INSTALL IT.
|
|
52
|
+
|
|
53
|
+
ONLY use try/except fallback if the specification says the feature is optional:
|
|
54
|
+
```python
|
|
55
|
+
try:
|
|
56
|
+
import toml
|
|
57
|
+
except ImportError:
|
|
58
|
+
toml = None # Only if spec says toml is optional
|
|
59
|
+
```
|
|
60
|
+
- TypeError/AttributeError: Check function signatures and method names
|
|
61
|
+
- AssertionError: Check if the test expectation or the code logic is wrong
|
|
62
|
+
- ZeroDivisionError/ValueError: Add proper error handling
|
|
63
|
+
- SyntaxError (unterminated string literal / unexpected character):
|
|
64
|
+
This often means the file has garbage appended at the end (common LLM extraction bug).
|
|
65
|
+
FIX: Read the end of the file and look for JSON-like metadata patterns such as:
|
|
66
|
+
- Lines starting with `"explanation":`, `"focus":`, `"description":`
|
|
67
|
+
- Lines with only `}}` or `]`
|
|
68
|
+
- Code lines ending with `",` followed by JSON keys
|
|
69
|
+
SOLUTION: Delete all the garbage lines at the end of the file to restore valid Python.
|
|
70
|
+
|
|
71
|
+
% EDIT POLICY:
|
|
72
|
+
- Keep changes minimal and directly related to the failure
|
|
73
|
+
- Prefer fixing import statements over creating new files
|
|
74
|
+
- Prefer fixing implementation bugs over weakening tests
|
|
75
|
+
- You MAY create new files if truly needed (e.g., __init__.py for packages)
|
|
76
|
+
|
|
77
|
+
% AFTER FIXING, OUTPUT VERIFICATION COMMAND:
|
|
78
|
+
<<<BEGIN_TESTCMD>>>
|
|
79
|
+
python -m pytest "{test_abs}" -q
|
|
80
|
+
<<<END_TESTCMD>>>
|
|
81
|
+
|
|
82
|
+
% IMPORTANT:
|
|
83
|
+
- Use your file tools to directly read and modify the files
|
|
84
|
+
- Do NOT output the full file contents - just make targeted edits
|
|
85
|
+
- The test command will be run automatically to verify your fix worked
|