pdd-cli 0.0.49__py3-none-any.whl → 0.0.50__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pdd-cli might be problematic. Click here for more details.
- pdd/__init__.py +4 -4
- pdd/bug_to_unit_test.py +2 -0
- pdd/cli.py +8 -1
- pdd/code_generator.py +3 -1
- pdd/context_generator.py +3 -1
- pdd/continue_generation.py +47 -7
- pdd/data/llm_model.csv +15 -16
- pdd/detect_change_main.py +2 -2
- pdd/generate_test.py +3 -1
- pdd/llm_invoke.py +441 -68
- pdd/load_prompt_template.py +30 -9
- pdd/pdd_completion.fish +2 -2
- pdd/pdd_completion.zsh +4 -4
- pdd/postprocess.py +2 -2
- pdd/prompts/extract_prompt_update_LLM.prompt +7 -8
- pdd/prompts/insert_includes_LLM.prompt +4 -4
- pdd/prompts/unfinished_prompt_LLM.prompt +85 -1
- pdd/summarize_directory.py +15 -2
- pdd/trace.py +131 -11
- pdd/trace_main.py +2 -2
- pdd/unfinished_prompt.py +41 -2
- {pdd_cli-0.0.49.dist-info → pdd_cli-0.0.50.dist-info}/METADATA +5 -3
- {pdd_cli-0.0.49.dist-info → pdd_cli-0.0.50.dist-info}/RECORD +26 -26
- {pdd_cli-0.0.49.dist-info → pdd_cli-0.0.50.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.49.dist-info → pdd_cli-0.0.50.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.49.dist-info → pdd_cli-0.0.50.dist-info}/licenses/LICENSE +0 -0
- {pdd_cli-0.0.49.dist-info → pdd_cli-0.0.50.dist-info}/top_level.txt +0 -0
pdd/load_prompt_template.py
CHANGED
|
@@ -23,18 +23,39 @@ def load_prompt_template(prompt_name: str) -> Optional[str]:
|
|
|
23
23
|
print_formatted("[red]Unexpected error loading prompt template[/red]")
|
|
24
24
|
return None
|
|
25
25
|
|
|
26
|
-
# Step 1: Get project path from environment variable
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
# Step 1: Get project path from environment variable (preferred),
|
|
27
|
+
# else fall back to auto-detect based on this module's location or CWD.
|
|
28
|
+
project_path_env = os.getenv('PDD_PATH')
|
|
29
|
+
candidate_paths = []
|
|
30
|
+
if project_path_env:
|
|
31
|
+
candidate_paths.append(Path(project_path_env))
|
|
32
|
+
|
|
33
|
+
# Fallback 1: repository root inferred from this module (pdd/ => repo root)
|
|
34
|
+
try:
|
|
35
|
+
module_root = Path(__file__).resolve().parent # pdd/
|
|
36
|
+
repo_root = module_root.parent # repo root
|
|
37
|
+
candidate_paths.append(repo_root)
|
|
38
|
+
except Exception:
|
|
39
|
+
pass
|
|
31
40
|
|
|
32
|
-
#
|
|
33
|
-
|
|
41
|
+
# Fallback 2: current working directory
|
|
42
|
+
candidate_paths.append(Path.cwd())
|
|
43
|
+
|
|
44
|
+
# Build candidate prompt paths to try in order
|
|
45
|
+
prompt_candidates = [cp / 'prompts' / f"{prompt_name}.prompt" for cp in candidate_paths]
|
|
34
46
|
|
|
35
47
|
# Step 2: Load and return the prompt template
|
|
36
|
-
|
|
37
|
-
|
|
48
|
+
prompt_path: Optional[Path] = None
|
|
49
|
+
for candidate in prompt_candidates:
|
|
50
|
+
if candidate.exists():
|
|
51
|
+
prompt_path = candidate
|
|
52
|
+
break
|
|
53
|
+
|
|
54
|
+
if prompt_path is None:
|
|
55
|
+
tried = "\n".join(str(c) for c in prompt_candidates)
|
|
56
|
+
print_formatted(
|
|
57
|
+
f"[red]Prompt file not found in any candidate locations for '{prompt_name}'. Tried:\n{tried}[/red]"
|
|
58
|
+
)
|
|
38
59
|
return None
|
|
39
60
|
|
|
40
61
|
try:
|
pdd/pdd_completion.fish
CHANGED
|
@@ -27,7 +27,7 @@ complete -c pdd -n "__fish_use_subcommand" -a conflicts -d "Analyze conflicts be
|
|
|
27
27
|
complete -c pdd -n "__fish_use_subcommand" -a crash -d "Fix code causing program crash"
|
|
28
28
|
complete -c pdd -n "__fish_use_subcommand" -a trace -d "Trace code line to prompt"
|
|
29
29
|
complete -c pdd -n "__fish_use_subcommand" -a bug -d "Generate unit test from bug report"
|
|
30
|
-
complete -c pdd -n "__fish_use_subcommand" -a auto-deps -d "Analyze and insert dependencies"
|
|
30
|
+
complete -c pdd -n "__fish_use_subcommand" -a auto-deps -d "Analyze and insert dependencies from directory or glob"
|
|
31
31
|
complete -c pdd -n "__fish_use_subcommand" -a verify -d "Verify functional correctness using LLM judgment"
|
|
32
32
|
|
|
33
33
|
# Command-specific completions
|
|
@@ -131,4 +131,4 @@ complete -c pdd -n "__fish_seen_subcommand_from generate example test preprocess
|
|
|
131
131
|
complete -c pdd -n "__fish_seen_subcommand_from generate example test preprocess fix split change update detect conflicts crash trace bug auto-deps verify" -a "(__fish_complete_suffix .log .txt .csv)"
|
|
132
132
|
|
|
133
133
|
# Help completion
|
|
134
|
-
complete -c pdd -n "__fish_seen_subcommand_from help" -a "generate example test preprocess fix split change update detect conflicts crash trace bug auto-deps verify" -d "Show help for specific command"
|
|
134
|
+
complete -c pdd -n "__fish_seen_subcommand_from help" -a "generate example test preprocess fix split change update detect conflicts crash trace bug auto-deps verify" -d "Show help for specific command"
|
pdd/pdd_completion.zsh
CHANGED
|
@@ -349,7 +349,7 @@ _pdd_bug() {
|
|
|
349
349
|
# --force-scan
|
|
350
350
|
# Args:
|
|
351
351
|
# 1: PROMPT_FILE
|
|
352
|
-
# 2: DIRECTORY_PATH
|
|
352
|
+
# 2: DIRECTORY_PATH (directory or glob pattern)
|
|
353
353
|
_pdd_auto_deps() {
|
|
354
354
|
_arguments -s \
|
|
355
355
|
$_pdd_global_opts \
|
|
@@ -357,7 +357,7 @@ _pdd_auto_deps() {
|
|
|
357
357
|
'--csv=[CSV file for dependency info (default: project_dependencies.csv).]:filename:_files' \
|
|
358
358
|
'--force-scan[Force rescanning of all potential dependency files.]' \
|
|
359
359
|
'1:prompt-file:_files' \
|
|
360
|
-
'2:directory:_files -/' \
|
|
360
|
+
'2:directory-or-glob:_files -/' \
|
|
361
361
|
'*:filename:_files'
|
|
362
362
|
}
|
|
363
363
|
|
|
@@ -410,7 +410,7 @@ _pdd() {
|
|
|
410
410
|
'crash:Fix errors in a code module and its calling program'
|
|
411
411
|
'trace:Find the prompt file line number associated with a code line'
|
|
412
412
|
'bug:Generate a unit test based on incorrect vs desired outputs'
|
|
413
|
-
'auto-deps:Analyze a prompt
|
|
413
|
+
'auto-deps:Analyze a prompt and include deps from a directory or glob'
|
|
414
414
|
'verify:Verify functional correctness using LLM judgment and iteratively fix'
|
|
415
415
|
)
|
|
416
416
|
|
|
@@ -487,4 +487,4 @@ else
|
|
|
487
487
|
echo >&2 "Warning: Could not register pdd completion. Make sure ZSH completion system is working."
|
|
488
488
|
fi
|
|
489
489
|
|
|
490
|
-
# End of pdd_completion.zsh
|
|
490
|
+
# End of pdd_completion.zsh
|
pdd/postprocess.py
CHANGED
|
@@ -3,7 +3,7 @@ from rich import print
|
|
|
3
3
|
from pydantic import BaseModel, Field
|
|
4
4
|
from .load_prompt_template import load_prompt_template
|
|
5
5
|
from .llm_invoke import llm_invoke
|
|
6
|
-
from . import DEFAULT_TIME
|
|
6
|
+
from . import DEFAULT_TIME, DEFAULT_STRENGTH
|
|
7
7
|
|
|
8
8
|
class ExtractedCode(BaseModel):
|
|
9
9
|
"""Pydantic model for the extracted code."""
|
|
@@ -36,7 +36,7 @@ def postprocess_0(text: str) -> str:
|
|
|
36
36
|
def postprocess(
|
|
37
37
|
llm_output: str,
|
|
38
38
|
language: str,
|
|
39
|
-
strength: float =
|
|
39
|
+
strength: float = DEFAULT_STRENGTH,
|
|
40
40
|
temperature: float = 0,
|
|
41
41
|
time: float = DEFAULT_TIME,
|
|
42
42
|
verbose: bool = False
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
% You are an expert Software Engineer. Your goal is to extract the updated prompt from the LLM output.
|
|
1
|
+
% You are an expert Software Engineer. Your goal is to extract the updated prompt from the LLM output in JSON format.
|
|
2
2
|
|
|
3
3
|
% Here is the generated llm_output: <llm_output>{llm_output}</llm_output>
|
|
4
4
|
|
|
5
|
-
% The LLM output contains the modified prompt that will generate the modified code, possibly with some additional commentary or explanation.
|
|
6
|
-
% Your task is to identify and extract ONLY the modified prompt itself, without adding any JSON structure or additional formatting.
|
|
5
|
+
% The LLM output contains the modified prompt that will generate the modified code, possibly with some additional commentary or explanation. Your task is to identify and extract ONLY the modified prompt itself, without adding any additional formatting.
|
|
7
6
|
|
|
8
7
|
% Ensure you:
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
1. Remove any "# Modified Prompt" headers or similar text that isn't part of the actual prompt
|
|
9
|
+
2. Preserve all markdown, code blocks, and formatting within the actual prompt
|
|
10
|
+
3. Don't add any explanatory text, JSON wrappers, or your own commentary
|
|
11
|
+
4. Return only the text that constitutes the actual prompt
|
|
13
12
|
|
|
14
|
-
% The "modified_prompt" should be the complete, standalone prompt that could be used directly to generate the modified code.
|
|
13
|
+
% The "modified_prompt" JSON key should be the complete, standalone prompt that could be used directly to generate the modified code.
|
|
@@ -75,8 +75,8 @@ def main():
|
|
|
75
75
|
temperature = 1
|
|
76
76
|
verbose = False
|
|
77
77
|
|
|
78
|
-
strength = 0.
|
|
79
|
-
while strength <=
|
|
78
|
+
strength = 0.0
|
|
79
|
+
while strength <= 1:
|
|
80
80
|
print(f"\nStrength: {strength}")
|
|
81
81
|
|
|
82
82
|
# Example 1: Unstructured Output
|
|
@@ -215,8 +215,8 @@ def main():
|
|
|
215
215
|
temperature = 1
|
|
216
216
|
verbose = False
|
|
217
217
|
|
|
218
|
-
strength = 0.
|
|
219
|
-
while strength <=
|
|
218
|
+
strength = 0.0
|
|
219
|
+
while strength <= 1:
|
|
220
220
|
print(f"\nStrength: {strength}")
|
|
221
221
|
|
|
222
222
|
# Example 1: Unstructured Output
|
|
@@ -1,18 +1,102 @@
|
|
|
1
1
|
% You are tasked with determining whether a given prompt has finished outputting everything or if it still needs to continue. This is crucial for ensuring that all necessary information has been provided before proceeding with further actions. You will often be provided the last few hundred characters of the prompt_text to analyze and determine if it appears to be complete or if it seems to be cut off or unfinished. You are just looking at the prompt_text and not the entire prompt file. The beginning part of the prompt_text is not always provided, so you will need to make a judgment based on the text you are given.
|
|
2
2
|
|
|
3
|
+
% IMPORTANT:
|
|
4
|
+
% - The prompt_text may contain code in various languages without Markdown fences.
|
|
5
|
+
% - Do NOT require triple backticks for completeness; judge the code/text itself.
|
|
6
|
+
% - Prefer concrete syntactic signals of completeness over stylistic ones.
|
|
7
|
+
|
|
3
8
|
% Here is the prompt text to analyze:
|
|
4
9
|
<prompt_text>
|
|
5
10
|
{PROMPT_TEXT}
|
|
6
11
|
</prompt_text>
|
|
7
12
|
|
|
13
|
+
% Optional language hint (may be empty or missing). If not provided, infer the language from the text:
|
|
14
|
+
<language>
|
|
15
|
+
{LANGUAGE}
|
|
16
|
+
</language>
|
|
17
|
+
|
|
8
18
|
% Carefully examine the provided prompt text and determine if it appears to be complete or if it seems to be cut off or unfinished. Consider the following factors:
|
|
9
19
|
1. Sentence structure: Are all sentences grammatically complete?
|
|
10
20
|
2. Content flow: Does the text end abruptly or does it have a natural conclusion?
|
|
11
21
|
3. Context: Based on the content, does it seem like all necessary information has been provided?
|
|
12
22
|
4. Formatting: Are there any unclosed parentheses, quotation marks, or other formatting issues that suggest incompleteness?
|
|
13
23
|
|
|
24
|
+
% Multi-language code completeness heuristics (apply when text looks like code):
|
|
25
|
+
- If the text forms a syntactically complete module/snippet for the language, treat it as finished (even without Markdown fences).
|
|
26
|
+
- Generic signals across languages:
|
|
27
|
+
* Balanced delimiters: (), [], {{}}, quotes, and block comments are closed.
|
|
28
|
+
* No mid-token/mid-statement tail: it does not end on `return a +`, `a =`, `def foo(`, `function f(`, trailing `.`, `->`, `::`, trailing `,`, or a line-continuation like `\\`.
|
|
29
|
+
* Block closure: constructs that open a block are closed (e.g., Python indentation after `:`, or matching `{{}}` in C/Java/JS/TS/Go).
|
|
30
|
+
- Language specifics (use LANGUAGE if given; otherwise infer from the text):
|
|
31
|
+
* Python: colon-introduced blocks closed; indentation consistent; triple-quoted strings balanced.
|
|
32
|
+
* JS/TS: braces and parentheses balanced; no dangling `export`/`import` without a following specifier; `/* ... */` comments closed.
|
|
33
|
+
* Java/C/C++/C#: braces and parentheses balanced; string/char literals closed; block comments closed.
|
|
34
|
+
* Go: braces balanced; no dangling keyword indicating an unfinished clause.
|
|
35
|
+
* HTML/XML: tags properly nested/closed; attributes properly quoted; no unfinished `<tag` or dangling `</`.
|
|
36
|
+
- If this is only the tail of a longer file, mark finished when the tail itself is syntactically complete and does not indicate a dangling continuation.
|
|
37
|
+
|
|
14
38
|
% Provide your reasoning for why you believe the prompt is complete or incomplete.
|
|
15
39
|
|
|
16
40
|
% Output a JSON object with two keys:
|
|
17
41
|
1. "reasoning": A string containing your structured reasoning
|
|
18
|
-
2. "is_finished": A boolean value (true if the prompt is complete, false if it's incomplete)
|
|
42
|
+
2. "is_finished": A boolean value (true if the prompt is complete, false if it's incomplete)
|
|
43
|
+
|
|
44
|
+
% Examples (concise):
|
|
45
|
+
<examples>
|
|
46
|
+
<example1>
|
|
47
|
+
<input>
|
|
48
|
+
<prompt_text>
|
|
49
|
+
def add(a, b):\n return a + b\n
|
|
50
|
+
</prompt_text>
|
|
51
|
+
</input>
|
|
52
|
+
<output>
|
|
53
|
+
{{"reasoning": "Python code parses; blocks and quotes are closed; ends on a complete return statement.", "is_finished": true}}
|
|
54
|
+
</output>
|
|
55
|
+
</example1>
|
|
56
|
+
<example2>
|
|
57
|
+
<input>
|
|
58
|
+
<prompt_text>
|
|
59
|
+
def add(a, b):\n return a +
|
|
60
|
+
</prompt_text>
|
|
61
|
+
</input>
|
|
62
|
+
<output>
|
|
63
|
+
{{"reasoning": "Ends mid-expression (`return a +`), indicates unfinished statement.", "is_finished": false}}
|
|
64
|
+
</output>
|
|
65
|
+
</example2>
|
|
66
|
+
<example3>
|
|
67
|
+
<input>
|
|
68
|
+
<prompt_text>
|
|
69
|
+
function add(a, b) {{\n return a + b;\n}}\n
|
|
70
|
+
</prompt_text>
|
|
71
|
+
<language>
|
|
72
|
+
JavaScript
|
|
73
|
+
</language>
|
|
74
|
+
</input>
|
|
75
|
+
<output>
|
|
76
|
+
{{"reasoning": "JS braces and parentheses balanced; ends at a statement boundary; no dangling tokens.", "is_finished": true}}
|
|
77
|
+
</output>
|
|
78
|
+
</example3>
|
|
79
|
+
<example4>
|
|
80
|
+
<input>
|
|
81
|
+
<prompt_text>
|
|
82
|
+
<div class=\"box\">Hello
|
|
83
|
+
</prompt_text>
|
|
84
|
+
<language>
|
|
85
|
+
HTML
|
|
86
|
+
</language>
|
|
87
|
+
</input>
|
|
88
|
+
<output>
|
|
89
|
+
{{"reasoning": "HTML tag not closed (missing </div>); attribute quotes OK but element is unclosed.", "is_finished": false}}
|
|
90
|
+
</output>
|
|
91
|
+
</example4>
|
|
92
|
+
<example5>
|
|
93
|
+
<input>
|
|
94
|
+
<prompt_text>
|
|
95
|
+
class C:\n def f(self):\n x = 1\n
|
|
96
|
+
</prompt_text>
|
|
97
|
+
</input>
|
|
98
|
+
<output>
|
|
99
|
+
{{"reasoning": "All blocks properly indented and closed in the visible tail; no dangling colon blocks or open delimiters; tail is syntactically complete.", "is_finished": true}}
|
|
100
|
+
</output>
|
|
101
|
+
</example5>
|
|
102
|
+
</examples>
|
pdd/summarize_directory.py
CHANGED
|
@@ -131,8 +131,21 @@ def summarize_directory(
|
|
|
131
131
|
raise ValueError("Invalid CSV file format.")
|
|
132
132
|
existing_data = parse_existing_csv(csv_file, verbose)
|
|
133
133
|
|
|
134
|
+
# Expand directory_path: support plain directories or glob patterns
|
|
135
|
+
try:
|
|
136
|
+
normalized_input = normalize_path(directory_path)
|
|
137
|
+
except Exception:
|
|
138
|
+
normalized_input = directory_path
|
|
139
|
+
|
|
140
|
+
if os.path.isdir(normalized_input):
|
|
141
|
+
# Recursively include all files under the directory
|
|
142
|
+
search_pattern = os.path.join(normalized_input, "**", "*")
|
|
143
|
+
else:
|
|
144
|
+
# Treat as a glob pattern (may be a single file path too)
|
|
145
|
+
search_pattern = directory_path
|
|
146
|
+
|
|
134
147
|
# Get list of files first to ensure consistent order
|
|
135
|
-
files = sorted(glob.glob(
|
|
148
|
+
files = sorted(glob.glob(search_pattern, recursive=True))
|
|
136
149
|
if not files:
|
|
137
150
|
if verbose:
|
|
138
151
|
print("[yellow]No files found.[/yellow]")
|
|
@@ -224,4 +237,4 @@ def summarize_directory(
|
|
|
224
237
|
|
|
225
238
|
except Exception as e:
|
|
226
239
|
print(f"[red]An error occurred: {str(e)}[/red]")
|
|
227
|
-
raise
|
|
240
|
+
raise
|
pdd/trace.py
CHANGED
|
@@ -3,6 +3,7 @@ from rich import print
|
|
|
3
3
|
from rich.console import Console
|
|
4
4
|
from pydantic import BaseModel, Field
|
|
5
5
|
import difflib
|
|
6
|
+
import re
|
|
6
7
|
from .load_prompt_template import load_prompt_template
|
|
7
8
|
from .preprocess import preprocess
|
|
8
9
|
from .llm_invoke import llm_invoke
|
|
@@ -102,29 +103,148 @@ def trace(
|
|
|
102
103
|
# Step 7: Find matching line in prompt file using fuzzy matching
|
|
103
104
|
prompt_lines = prompt_file.splitlines()
|
|
104
105
|
best_match = None
|
|
105
|
-
highest_ratio = 0
|
|
106
|
+
highest_ratio = 0.0
|
|
106
107
|
|
|
107
108
|
if verbose:
|
|
108
109
|
console.print(f"Searching for line: {prompt_line_str}")
|
|
109
110
|
|
|
110
|
-
|
|
111
|
+
# Robust normalization for comparison
|
|
112
|
+
def normalize_text(s: str) -> str:
|
|
113
|
+
if s is None:
|
|
114
|
+
return ""
|
|
115
|
+
s = s.replace("\u201c", '"').replace("\u201d", '"') # smart double quotes → straight
|
|
116
|
+
s = s.replace("\u2018", "'").replace("\u2019", "'") # smart single quotes → straight
|
|
117
|
+
s = s.replace("\u00A0", " ") # non-breaking space → space
|
|
118
|
+
s = re.sub(r"\s+", " ", s.strip()) # collapse whitespace
|
|
119
|
+
return s
|
|
120
|
+
|
|
121
|
+
# If the model echoed wrapper tags like <llm_output>...</llm_output>, extract inner text
|
|
122
|
+
raw_search = prompt_line_str
|
|
123
|
+
try:
|
|
124
|
+
m = re.search(r"<\s*llm_output\s*>(.*?)<\s*/\s*llm_output\s*>", raw_search, flags=re.IGNORECASE | re.DOTALL)
|
|
125
|
+
if m:
|
|
126
|
+
raw_search = m.group(1)
|
|
127
|
+
except Exception:
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
normalized_search = normalize_text(raw_search).casefold()
|
|
131
|
+
best_candidate_idx = None
|
|
132
|
+
best_candidate_len = 0
|
|
111
133
|
|
|
112
134
|
for i, line in enumerate(prompt_lines, 1):
|
|
113
|
-
normalized_line = line.
|
|
135
|
+
normalized_line = normalize_text(line).casefold()
|
|
136
|
+
line_len = len(normalized_line)
|
|
137
|
+
|
|
138
|
+
# Base similarity
|
|
114
139
|
ratio = difflib.SequenceMatcher(None, normalized_search, normalized_line).ratio()
|
|
115
140
|
|
|
141
|
+
# Boost if one contains the other, but avoid trivial/short lines
|
|
142
|
+
if normalized_search and line_len >= 8:
|
|
143
|
+
shorter = min(len(normalized_search), line_len)
|
|
144
|
+
longer = max(len(normalized_search), line_len)
|
|
145
|
+
length_ratio = shorter / longer if longer else 0.0
|
|
146
|
+
if length_ratio >= 0.4 and (
|
|
147
|
+
normalized_search in normalized_line or normalized_line in normalized_search
|
|
148
|
+
):
|
|
149
|
+
ratio = max(ratio, 0.999)
|
|
150
|
+
|
|
116
151
|
if verbose:
|
|
117
152
|
console.print(f"Line {i}: '{line}' - Match ratio: {ratio}")
|
|
118
153
|
|
|
119
|
-
#
|
|
120
|
-
if
|
|
121
|
-
|
|
122
|
-
if normalized_search == normalized_line:
|
|
154
|
+
# Track best candidate overall, skipping empty lines
|
|
155
|
+
if line_len > 0:
|
|
156
|
+
if ratio > highest_ratio:
|
|
123
157
|
highest_ratio = ratio
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
highest_ratio
|
|
158
|
+
best_candidate_idx = i
|
|
159
|
+
best_candidate_len = line_len
|
|
160
|
+
elif abs(ratio - highest_ratio) < 1e-6 and best_candidate_idx is not None:
|
|
161
|
+
# Tie-breaker: prefer longer normalized line
|
|
162
|
+
if line_len > best_candidate_len:
|
|
163
|
+
best_candidate_idx = i
|
|
164
|
+
best_candidate_len = line_len
|
|
165
|
+
|
|
166
|
+
# Early exit on exact normalized equality
|
|
167
|
+
if normalized_search == normalized_line:
|
|
127
168
|
best_match = i
|
|
169
|
+
highest_ratio = 1.0
|
|
170
|
+
break
|
|
171
|
+
|
|
172
|
+
# Decide on acceptance thresholds
|
|
173
|
+
primary_threshold = 0.8 # lowered threshold for normal acceptance
|
|
174
|
+
fallback_threshold = 0.6 # low-confidence fallback threshold
|
|
175
|
+
|
|
176
|
+
if best_match is None and best_candidate_idx is not None:
|
|
177
|
+
if highest_ratio >= primary_threshold:
|
|
178
|
+
best_match = best_candidate_idx
|
|
179
|
+
elif highest_ratio >= fallback_threshold:
|
|
180
|
+
best_match = best_candidate_idx
|
|
181
|
+
if verbose:
|
|
182
|
+
console.print(
|
|
183
|
+
f"[yellow]Low-confidence match selected (ratio={highest_ratio:.3f}).[/yellow]"
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Step 7b: Multi-line window matching (sizes 2 and 3) if no strong single-line match
|
|
187
|
+
if (best_match is None) or (highest_ratio < primary_threshold):
|
|
188
|
+
if verbose:
|
|
189
|
+
console.print("[blue]No strong single-line match; trying multi-line windows...[/blue]")
|
|
190
|
+
|
|
191
|
+
win_best_ratio = 0.0
|
|
192
|
+
win_best_idx: Optional[int] = None
|
|
193
|
+
win_best_size = 0
|
|
194
|
+
|
|
195
|
+
for window_size in (2, 3):
|
|
196
|
+
if len(prompt_lines) < window_size:
|
|
197
|
+
continue
|
|
198
|
+
for start_idx in range(1, len(prompt_lines) - window_size + 2):
|
|
199
|
+
window_lines = prompt_lines[start_idx - 1 : start_idx - 1 + window_size]
|
|
200
|
+
window_text = " ".join(window_lines)
|
|
201
|
+
normalized_window = normalize_text(window_text).casefold()
|
|
202
|
+
seg_len = len(normalized_window)
|
|
203
|
+
if seg_len == 0:
|
|
204
|
+
continue
|
|
205
|
+
|
|
206
|
+
ratio = difflib.SequenceMatcher(None, normalized_search, normalized_window).ratio()
|
|
207
|
+
|
|
208
|
+
# Containment boost under similar length condition
|
|
209
|
+
shorter = min(len(normalized_search), seg_len)
|
|
210
|
+
longer = max(len(normalized_search), seg_len)
|
|
211
|
+
length_ratio = (shorter / longer) if longer else 0.0
|
|
212
|
+
if (
|
|
213
|
+
normalized_search
|
|
214
|
+
and seg_len >= 8
|
|
215
|
+
and length_ratio >= 0.4
|
|
216
|
+
and (
|
|
217
|
+
normalized_search in normalized_window
|
|
218
|
+
or normalized_window in normalized_search
|
|
219
|
+
)
|
|
220
|
+
):
|
|
221
|
+
ratio = max(ratio, 0.999)
|
|
222
|
+
|
|
223
|
+
if verbose:
|
|
224
|
+
console.print(
|
|
225
|
+
f"Window {start_idx}-{start_idx+window_size-1}: ratio={ratio}"
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# Track best window, prefer higher ratio; tie-breaker: larger window, then longer segment
|
|
229
|
+
if ratio > win_best_ratio + 1e-6 or (
|
|
230
|
+
abs(ratio - win_best_ratio) < 1e-6
|
|
231
|
+
and (window_size > win_best_size or (window_size == win_best_size and seg_len > 0))
|
|
232
|
+
):
|
|
233
|
+
win_best_ratio = ratio
|
|
234
|
+
win_best_idx = start_idx
|
|
235
|
+
win_best_size = window_size
|
|
236
|
+
|
|
237
|
+
if win_best_idx is not None and win_best_ratio > highest_ratio:
|
|
238
|
+
if win_best_ratio >= primary_threshold:
|
|
239
|
+
best_match = win_best_idx
|
|
240
|
+
highest_ratio = win_best_ratio
|
|
241
|
+
elif win_best_ratio >= fallback_threshold and best_match is None:
|
|
242
|
+
best_match = win_best_idx
|
|
243
|
+
highest_ratio = win_best_ratio
|
|
244
|
+
if verbose:
|
|
245
|
+
console.print(
|
|
246
|
+
f"[yellow]Low-confidence multi-line match selected (ratio={win_best_ratio:.3f}).[/yellow]"
|
|
247
|
+
)
|
|
128
248
|
|
|
129
249
|
# Step 8: Return results
|
|
130
250
|
if verbose:
|
|
@@ -136,4 +256,4 @@ def trace(
|
|
|
136
256
|
|
|
137
257
|
except Exception as e:
|
|
138
258
|
console.print(f"[bold red]Error in trace function: {str(e)}[/bold red]")
|
|
139
|
-
return None, 0.0, ""
|
|
259
|
+
return None, 0.0, ""
|
pdd/trace_main.py
CHANGED
|
@@ -5,7 +5,7 @@ import os
|
|
|
5
5
|
import logging
|
|
6
6
|
from .construct_paths import construct_paths
|
|
7
7
|
from .trace import trace
|
|
8
|
-
from . import DEFAULT_TIME, DEFAULT_STRENGTH
|
|
8
|
+
from . import DEFAULT_TIME, DEFAULT_STRENGTH, DEFAULT_TEMPERATURE
|
|
9
9
|
logging.basicConfig(level=logging.WARNING)
|
|
10
10
|
logger = logging.getLogger(__name__)
|
|
11
11
|
|
|
@@ -50,7 +50,7 @@ def trace_main(ctx: click.Context, prompt_file: str, code_file: str, code_line:
|
|
|
50
50
|
|
|
51
51
|
# Perform trace analysis
|
|
52
52
|
strength = ctx.obj.get('strength', DEFAULT_STRENGTH)
|
|
53
|
-
temperature = ctx.obj.get('temperature',
|
|
53
|
+
temperature = ctx.obj.get('temperature', DEFAULT_TEMPERATURE)
|
|
54
54
|
time = ctx.obj.get('time', DEFAULT_TIME)
|
|
55
55
|
try:
|
|
56
56
|
prompt_line, total_cost, model_name = trace(
|
pdd/unfinished_prompt.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from typing import Tuple
|
|
1
|
+
from typing import Tuple, Optional
|
|
2
|
+
import ast
|
|
2
3
|
from pydantic import BaseModel, Field
|
|
3
4
|
from rich import print as rprint
|
|
4
5
|
from .load_prompt_template import load_prompt_template
|
|
@@ -14,6 +15,7 @@ def unfinished_prompt(
|
|
|
14
15
|
strength: float = DEFAULT_STRENGTH,
|
|
15
16
|
temperature: float = 0,
|
|
16
17
|
time: float = DEFAULT_TIME,
|
|
18
|
+
language: Optional[str] = None,
|
|
17
19
|
verbose: bool = False
|
|
18
20
|
) -> Tuple[str, bool, float, str]:
|
|
19
21
|
"""
|
|
@@ -48,6 +50,40 @@ def unfinished_prompt(
|
|
|
48
50
|
if not 0 <= temperature <= 1:
|
|
49
51
|
raise ValueError("Temperature must be between 0 and 1")
|
|
50
52
|
|
|
53
|
+
# Step 0: Fast syntactic completeness check for Python tails
|
|
54
|
+
# Apply when language explicitly 'python' or when the text likely looks like Python.
|
|
55
|
+
def _looks_like_python(text: str) -> bool:
|
|
56
|
+
lowered = text.strip().lower()
|
|
57
|
+
py_signals = (
|
|
58
|
+
"def ", "class ", "import ", "from ",
|
|
59
|
+
)
|
|
60
|
+
if any(sig in lowered for sig in py_signals):
|
|
61
|
+
return True
|
|
62
|
+
# Heuristic: has 'return ' without JS/TS markers
|
|
63
|
+
if "return " in lowered and not any(tok in lowered for tok in ("function", "=>", ";", "{", "}")):
|
|
64
|
+
return True
|
|
65
|
+
# Heuristic: colon-introduced blocks and indentation
|
|
66
|
+
if ":\n" in text or "\n " in text:
|
|
67
|
+
return True
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
should_try_python_parse = (language or "").lower() == "python" or _looks_like_python(prompt_text)
|
|
71
|
+
if should_try_python_parse:
|
|
72
|
+
try:
|
|
73
|
+
ast.parse(prompt_text)
|
|
74
|
+
reasoning = "Syntactic Python check passed (ast.parse succeeded); treating as finished."
|
|
75
|
+
if verbose:
|
|
76
|
+
rprint("[green]" + reasoning + "[/green]")
|
|
77
|
+
return (
|
|
78
|
+
reasoning,
|
|
79
|
+
True,
|
|
80
|
+
0.0,
|
|
81
|
+
"syntactic_check"
|
|
82
|
+
)
|
|
83
|
+
except SyntaxError:
|
|
84
|
+
# Fall through to LLM-based judgment
|
|
85
|
+
pass
|
|
86
|
+
|
|
51
87
|
# Step 1: Load the prompt template
|
|
52
88
|
if verbose:
|
|
53
89
|
rprint("[blue]Loading prompt template...[/blue]")
|
|
@@ -58,6 +94,9 @@ def unfinished_prompt(
|
|
|
58
94
|
|
|
59
95
|
# Step 2: Prepare input and invoke LLM
|
|
60
96
|
input_json = {"PROMPT_TEXT": prompt_text}
|
|
97
|
+
# Optionally pass a language hint to the prompt
|
|
98
|
+
if language:
|
|
99
|
+
input_json["LANGUAGE"] = language
|
|
61
100
|
|
|
62
101
|
if verbose:
|
|
63
102
|
rprint("[blue]Invoking LLM model...[/blue]")
|
|
@@ -116,4 +155,4 @@ if __name__ == "__main__":
|
|
|
116
155
|
rprint(f"Cost: ${cost:.6f}")
|
|
117
156
|
rprint(f"Model: {model}")
|
|
118
157
|
except Exception as e:
|
|
119
|
-
rprint("[red]Error in example:[/red]", str(e))
|
|
158
|
+
rprint("[red]Error in example:[/red]", str(e))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pdd-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.50
|
|
4
4
|
Summary: PDD (Prompt-Driven Development) Command Line Interface
|
|
5
5
|
Author: Greg Tanaka
|
|
6
6
|
Author-email: glt@alumni.caltech.edu
|
|
@@ -45,9 +45,11 @@ Requires-Dist: pytest-cov; extra == "dev"
|
|
|
45
45
|
Requires-Dist: pytest-mock; extra == "dev"
|
|
46
46
|
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
47
47
|
Requires-Dist: z3-solver; extra == "dev"
|
|
48
|
+
Requires-Dist: build; extra == "dev"
|
|
49
|
+
Requires-Dist: twine; extra == "dev"
|
|
48
50
|
Dynamic: license-file
|
|
49
51
|
|
|
50
|
-
.. image:: https://img.shields.io/badge/pdd--cli-v0.0.
|
|
52
|
+
.. image:: https://img.shields.io/badge/pdd--cli-v0.0.50-blue
|
|
51
53
|
:alt: PDD-CLI Version
|
|
52
54
|
|
|
53
55
|
.. image:: https://img.shields.io/badge/Discord-join%20chat-7289DA.svg?logo=discord&logoColor=white&link=https://discord.gg/Yp4RTh8bG7
|
|
@@ -124,7 +126,7 @@ After installation, verify:
|
|
|
124
126
|
|
|
125
127
|
pdd --version
|
|
126
128
|
|
|
127
|
-
You'll see the current PDD version (e.g., 0.0.
|
|
129
|
+
You'll see the current PDD version (e.g., 0.0.50).
|
|
128
130
|
|
|
129
131
|
Getting Started with Examples
|
|
130
132
|
-----------------------------
|