pdd-cli 0.0.2__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 +0 -0
- pdd/auto_deps_main.py +98 -0
- pdd/auto_include.py +175 -0
- pdd/auto_update.py +73 -0
- pdd/bug_main.py +99 -0
- pdd/bug_to_unit_test.py +159 -0
- pdd/change.py +141 -0
- pdd/change_main.py +240 -0
- pdd/cli.py +607 -0
- pdd/cmd_test_main.py +155 -0
- pdd/code_generator.py +117 -0
- pdd/code_generator_main.py +66 -0
- pdd/comment_line.py +35 -0
- pdd/conflicts_in_prompts.py +143 -0
- pdd/conflicts_main.py +90 -0
- pdd/construct_paths.py +251 -0
- pdd/context_generator.py +133 -0
- pdd/context_generator_main.py +73 -0
- pdd/continue_generation.py +140 -0
- pdd/crash_main.py +127 -0
- pdd/data/language_format.csv +61 -0
- pdd/data/llm_model.csv +15 -0
- pdd/detect_change.py +142 -0
- pdd/detect_change_main.py +100 -0
- pdd/find_section.py +28 -0
- pdd/fix_code_loop.py +212 -0
- pdd/fix_code_module_errors.py +143 -0
- pdd/fix_error_loop.py +216 -0
- pdd/fix_errors_from_unit_tests.py +240 -0
- pdd/fix_main.py +138 -0
- pdd/generate_output_paths.py +194 -0
- pdd/generate_test.py +140 -0
- pdd/get_comment.py +55 -0
- pdd/get_extension.py +52 -0
- pdd/get_language.py +41 -0
- pdd/git_update.py +84 -0
- pdd/increase_tests.py +93 -0
- pdd/insert_includes.py +150 -0
- pdd/llm_invoke.py +304 -0
- pdd/load_prompt_template.py +59 -0
- pdd/pdd_completion.fish +72 -0
- pdd/pdd_completion.sh +141 -0
- pdd/pdd_completion.zsh +418 -0
- pdd/postprocess.py +121 -0
- pdd/postprocess_0.py +52 -0
- pdd/preprocess.py +199 -0
- pdd/preprocess_main.py +72 -0
- pdd/process_csv_change.py +182 -0
- pdd/prompts/auto_include_LLM.prompt +230 -0
- pdd/prompts/bug_to_unit_test_LLM.prompt +17 -0
- pdd/prompts/change_LLM.prompt +34 -0
- pdd/prompts/conflict_LLM.prompt +23 -0
- pdd/prompts/continue_generation_LLM.prompt +3 -0
- pdd/prompts/detect_change_LLM.prompt +65 -0
- pdd/prompts/example_generator_LLM.prompt +10 -0
- pdd/prompts/extract_auto_include_LLM.prompt +6 -0
- pdd/prompts/extract_code_LLM.prompt +22 -0
- pdd/prompts/extract_conflict_LLM.prompt +19 -0
- pdd/prompts/extract_detect_change_LLM.prompt +19 -0
- pdd/prompts/extract_program_code_fix_LLM.prompt +16 -0
- pdd/prompts/extract_prompt_change_LLM.prompt +7 -0
- pdd/prompts/extract_prompt_split_LLM.prompt +9 -0
- pdd/prompts/extract_prompt_update_LLM.prompt +8 -0
- pdd/prompts/extract_promptline_LLM.prompt +11 -0
- pdd/prompts/extract_unit_code_fix_LLM.prompt +332 -0
- pdd/prompts/extract_xml_LLM.prompt +7 -0
- pdd/prompts/fix_code_module_errors_LLM.prompt +17 -0
- pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +62 -0
- pdd/prompts/generate_test_LLM.prompt +12 -0
- pdd/prompts/increase_tests_LLM.prompt +16 -0
- pdd/prompts/insert_includes_LLM.prompt +30 -0
- pdd/prompts/split_LLM.prompt +94 -0
- pdd/prompts/summarize_file_LLM.prompt +11 -0
- pdd/prompts/trace_LLM.prompt +30 -0
- pdd/prompts/trim_results_LLM.prompt +83 -0
- pdd/prompts/trim_results_start_LLM.prompt +45 -0
- pdd/prompts/unfinished_prompt_LLM.prompt +18 -0
- pdd/prompts/update_prompt_LLM.prompt +19 -0
- pdd/prompts/xml_convertor_LLM.prompt +54 -0
- pdd/split.py +119 -0
- pdd/split_main.py +103 -0
- pdd/summarize_directory.py +212 -0
- pdd/trace.py +135 -0
- pdd/trace_main.py +108 -0
- pdd/track_cost.py +102 -0
- pdd/unfinished_prompt.py +114 -0
- pdd/update_main.py +96 -0
- pdd/update_prompt.py +115 -0
- pdd/xml_tagger.py +122 -0
- pdd_cli-0.0.2.dist-info/LICENSE +7 -0
- pdd_cli-0.0.2.dist-info/METADATA +225 -0
- pdd_cli-0.0.2.dist-info/RECORD +95 -0
- pdd_cli-0.0.2.dist-info/WHEEL +5 -0
- pdd_cli-0.0.2.dist-info/entry_points.txt +2 -0
- pdd_cli-0.0.2.dist-info/top_level.txt +1 -0
pdd/cli.py
ADDED
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import importlib.resources
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from functools import wraps
|
|
6
|
+
from typing import Callable, List, Optional, Tuple
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
from rich import print as rprint
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.panel import Panel
|
|
12
|
+
|
|
13
|
+
# ----------------------------------------------------------------------
|
|
14
|
+
# Dynamically determine PDD_PATH at runtime.
|
|
15
|
+
# ----------------------------------------------------------------------
|
|
16
|
+
def get_local_pdd_path() -> str:
|
|
17
|
+
"""
|
|
18
|
+
Return the PDD_PATH directory.
|
|
19
|
+
First check the environment variable. If not set, attempt to
|
|
20
|
+
deduce it via importlib.resources. If that fails, abort.
|
|
21
|
+
"""
|
|
22
|
+
if "PDD_PATH" in os.environ:
|
|
23
|
+
return os.environ["PDD_PATH"]
|
|
24
|
+
else:
|
|
25
|
+
try:
|
|
26
|
+
with importlib.resources.path("pdd", "cli.py") as p:
|
|
27
|
+
fallback_path = str(p.parent)
|
|
28
|
+
# Also set it back into the environment for consistency
|
|
29
|
+
os.environ["PDD_PATH"] = fallback_path
|
|
30
|
+
return fallback_path
|
|
31
|
+
except ImportError:
|
|
32
|
+
rprint(
|
|
33
|
+
"[red]Error: Could not determine the path to the 'pdd' package. "
|
|
34
|
+
"Please set the PDD_PATH environment variable manually.[/red]"
|
|
35
|
+
)
|
|
36
|
+
sys.exit(1)
|
|
37
|
+
|
|
38
|
+
get_local_pdd_path()
|
|
39
|
+
# ----------------------------------------------------------------------
|
|
40
|
+
# Import sub-command modules
|
|
41
|
+
# ----------------------------------------------------------------------
|
|
42
|
+
from .code_generator_main import code_generator_main
|
|
43
|
+
from .context_generator_main import context_generator_main
|
|
44
|
+
from .cmd_test_main import cmd_test_main
|
|
45
|
+
from .preprocess_main import preprocess_main
|
|
46
|
+
from .fix_main import fix_main
|
|
47
|
+
from .split_main import split_main
|
|
48
|
+
from .change_main import change_main
|
|
49
|
+
from .update_main import update_main
|
|
50
|
+
from .detect_change_main import detect_change_main
|
|
51
|
+
from .conflicts_main import conflicts_main
|
|
52
|
+
from .crash_main import crash_main
|
|
53
|
+
from .trace_main import trace_main
|
|
54
|
+
from .bug_main import bug_main
|
|
55
|
+
from .track_cost import track_cost
|
|
56
|
+
from .auto_update import auto_update
|
|
57
|
+
from .auto_deps_main import auto_deps_main
|
|
58
|
+
|
|
59
|
+
console = Console()
|
|
60
|
+
|
|
61
|
+
@click.group()
|
|
62
|
+
@click.option("--force", is_flag=True, help="Overwrite existing files without asking for confirmation.")
|
|
63
|
+
@click.option("--strength", type=float, default=0.5, help="Set the strength of the AI model (0.0 to 1.0).")
|
|
64
|
+
@click.option("--temperature", type=float, default=0.0, help="Set the temperature of the AI model.")
|
|
65
|
+
@click.option("--verbose", is_flag=True, help="Increase output verbosity for more detailed information.")
|
|
66
|
+
@click.option("--quiet", is_flag=True, help="Decrease output verbosity for minimal information.")
|
|
67
|
+
@click.option("--output-cost", type=click.Path(), help="Enable cost tracking and output a CSV file with usage details.")
|
|
68
|
+
@click.option("--review-examples", is_flag=True,
|
|
69
|
+
help="Review and optionally exclude few-shot examples before command execution.")
|
|
70
|
+
@click.version_option(version="0.0.2")
|
|
71
|
+
@click.pass_context
|
|
72
|
+
def cli(
|
|
73
|
+
ctx,
|
|
74
|
+
force: bool,
|
|
75
|
+
strength: float,
|
|
76
|
+
temperature: float,
|
|
77
|
+
verbose: bool,
|
|
78
|
+
quiet: bool,
|
|
79
|
+
output_cost: Optional[str],
|
|
80
|
+
review_examples: bool,
|
|
81
|
+
):
|
|
82
|
+
"""
|
|
83
|
+
PDD (Prompt-Driven Development) Command Line Interface
|
|
84
|
+
"""
|
|
85
|
+
ctx.ensure_object(dict)
|
|
86
|
+
ctx.obj["force"] = force
|
|
87
|
+
ctx.obj["strength"] = strength
|
|
88
|
+
ctx.obj["temperature"] = temperature
|
|
89
|
+
ctx.obj["verbose"] = verbose
|
|
90
|
+
ctx.obj["quiet"] = quiet
|
|
91
|
+
ctx.obj["output_cost"] = output_cost or os.environ.get("PDD_OUTPUT_COST_PATH")
|
|
92
|
+
ctx.obj["review_examples"] = review_examples
|
|
93
|
+
|
|
94
|
+
# Auto-update check, but handle EOF errors so tests do not crash.
|
|
95
|
+
auto_update_enabled = os.environ.get("PDD_AUTO_UPDATE", "true").lower() == "true"
|
|
96
|
+
if auto_update_enabled and sys.stdin.isatty():
|
|
97
|
+
try:
|
|
98
|
+
auto_update()
|
|
99
|
+
except EOFError:
|
|
100
|
+
pass
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@cli.command()
|
|
104
|
+
@click.argument("prompt_file", type=click.Path(exists=True))
|
|
105
|
+
@click.option("--output", type=click.Path(), help="Specify where to save the generated code.")
|
|
106
|
+
@click.pass_context
|
|
107
|
+
@track_cost
|
|
108
|
+
def generate(ctx, prompt_file: str, output: Optional[str]) -> Tuple[str, float, str]:
|
|
109
|
+
"""Create runnable code from a prompt file."""
|
|
110
|
+
return code_generator_main(ctx, prompt_file, output)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@cli.command()
|
|
114
|
+
@click.argument("prompt_file", type=click.Path(exists=True))
|
|
115
|
+
@click.argument("code_file", type=click.Path(exists=True))
|
|
116
|
+
@click.option("--output", type=click.Path(), help="Specify where to save the generated example code.")
|
|
117
|
+
@click.pass_context
|
|
118
|
+
@track_cost
|
|
119
|
+
def example(
|
|
120
|
+
ctx,
|
|
121
|
+
prompt_file: str,
|
|
122
|
+
code_file: str,
|
|
123
|
+
output: Optional[str]
|
|
124
|
+
) -> Tuple[str, float, str]:
|
|
125
|
+
"""Create an example file from an existing code file and the prompt that generated it."""
|
|
126
|
+
return context_generator_main(ctx, prompt_file, code_file, output)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@cli.command()
|
|
130
|
+
@click.argument("prompt_file", type=click.Path(exists=True))
|
|
131
|
+
@click.argument("code_file", type=click.Path(exists=True))
|
|
132
|
+
@click.option("--output", type=click.Path(), help="Specify where to save the generated test file.")
|
|
133
|
+
@click.option("--language", help="Specify the programming language.")
|
|
134
|
+
@click.option(
|
|
135
|
+
"--coverage-report",
|
|
136
|
+
type=click.Path(exists=True),
|
|
137
|
+
default=None,
|
|
138
|
+
help="Path to a coverage report for enhancing tests."
|
|
139
|
+
)
|
|
140
|
+
@click.option(
|
|
141
|
+
"--existing-tests",
|
|
142
|
+
type=click.Path(exists=True),
|
|
143
|
+
default=None,
|
|
144
|
+
help="Existing test file to merge or build upon."
|
|
145
|
+
)
|
|
146
|
+
@click.option("--target-coverage", type=float, default=None, help="Desired coverage percentage.")
|
|
147
|
+
@click.option("--merge", is_flag=True, default=False, help="Merge new tests into existing tests.")
|
|
148
|
+
@click.pass_context
|
|
149
|
+
@track_cost
|
|
150
|
+
def test(
|
|
151
|
+
ctx,
|
|
152
|
+
prompt_file: str,
|
|
153
|
+
code_file: str,
|
|
154
|
+
output: Optional[str],
|
|
155
|
+
language: Optional[str],
|
|
156
|
+
coverage_report: Optional[str],
|
|
157
|
+
existing_tests: Optional[str],
|
|
158
|
+
target_coverage: Optional[float],
|
|
159
|
+
merge: bool,
|
|
160
|
+
) -> Tuple[str, float, str]:
|
|
161
|
+
"""
|
|
162
|
+
Generate or enhance unit tests for a given code file and its corresponding prompt file.
|
|
163
|
+
"""
|
|
164
|
+
return cmd_test_main(
|
|
165
|
+
ctx,
|
|
166
|
+
prompt_file,
|
|
167
|
+
code_file,
|
|
168
|
+
output,
|
|
169
|
+
language,
|
|
170
|
+
coverage_report,
|
|
171
|
+
existing_tests,
|
|
172
|
+
target_coverage,
|
|
173
|
+
merge,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@cli.command()
|
|
178
|
+
@click.argument("prompt_file", type=click.Path(exists=True))
|
|
179
|
+
@click.option("--output", type=click.Path(), help="Specify where to save the preprocessed prompt file.")
|
|
180
|
+
@click.option("--xml", is_flag=True, help="Automatically insert XML delimiters for complex prompts.")
|
|
181
|
+
@click.option("--recursive", is_flag=True, help="Recursively preprocess all prompt files in the prompt file.")
|
|
182
|
+
@click.option("--double", is_flag=True, help="Curly brackets will be doubled.")
|
|
183
|
+
@click.option("--exclude", multiple=True, help="List of keys to exclude from curly bracket doubling.")
|
|
184
|
+
@click.pass_context
|
|
185
|
+
@track_cost
|
|
186
|
+
def preprocess(
|
|
187
|
+
ctx,
|
|
188
|
+
prompt_file: str,
|
|
189
|
+
output: Optional[str],
|
|
190
|
+
xml: bool,
|
|
191
|
+
recursive: bool,
|
|
192
|
+
double: bool,
|
|
193
|
+
exclude: List[str]
|
|
194
|
+
) -> Tuple[str, float, str]:
|
|
195
|
+
"""Preprocess prompt files and save the results."""
|
|
196
|
+
return preprocess_main(ctx, prompt_file, output, xml, recursive, double, exclude)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@cli.command()
|
|
200
|
+
@click.argument("prompt_file", type=click.Path(exists=True))
|
|
201
|
+
@click.argument("code_file", type=click.Path(exists=True))
|
|
202
|
+
@click.argument("unit_test_file", type=click.Path(exists=True))
|
|
203
|
+
@click.argument("error_file", type=click.Path(exists=False))
|
|
204
|
+
@click.option("--output-test", type=click.Path(), help="Where to save the fixed unit test file.")
|
|
205
|
+
@click.option("--output-code", type=click.Path(), help="Where to save the fixed code file.")
|
|
206
|
+
@click.option(
|
|
207
|
+
"--output-results",
|
|
208
|
+
type=click.Path(),
|
|
209
|
+
help="Where to save the results from the error fixing process."
|
|
210
|
+
)
|
|
211
|
+
@click.option("--loop", is_flag=True, help="Enable iterative fixing process.")
|
|
212
|
+
@click.option(
|
|
213
|
+
"--verification-program",
|
|
214
|
+
type=click.Path(exists=True),
|
|
215
|
+
help="Path to a Python program that verifies code correctness."
|
|
216
|
+
)
|
|
217
|
+
@click.option("--max-attempts", type=int, default=3, help="Maximum fix attempts before giving up.")
|
|
218
|
+
@click.option("--budget", type=float, default=5.0, help="Maximum cost allowed for the fixing process.")
|
|
219
|
+
@click.option(
|
|
220
|
+
"--auto-submit",
|
|
221
|
+
is_flag=True,
|
|
222
|
+
help="Automatically submit the example if all unit tests pass during the fix loop."
|
|
223
|
+
)
|
|
224
|
+
@click.pass_context
|
|
225
|
+
@track_cost
|
|
226
|
+
def fix(
|
|
227
|
+
ctx,
|
|
228
|
+
prompt_file: str,
|
|
229
|
+
code_file: str,
|
|
230
|
+
unit_test_file: str,
|
|
231
|
+
error_file: str,
|
|
232
|
+
output_test: Optional[str],
|
|
233
|
+
output_code: Optional[str],
|
|
234
|
+
output_results: Optional[str],
|
|
235
|
+
loop: bool,
|
|
236
|
+
verification_program: Optional[str],
|
|
237
|
+
max_attempts: int,
|
|
238
|
+
budget: float,
|
|
239
|
+
auto_submit: bool
|
|
240
|
+
) -> Tuple[bool, str, str, int, float, str]:
|
|
241
|
+
"""Fix errors in code and unit tests based on error messages and the original prompt file."""
|
|
242
|
+
return fix_main(
|
|
243
|
+
ctx,
|
|
244
|
+
prompt_file,
|
|
245
|
+
code_file,
|
|
246
|
+
unit_test_file,
|
|
247
|
+
error_file,
|
|
248
|
+
output_test,
|
|
249
|
+
output_code,
|
|
250
|
+
output_results,
|
|
251
|
+
loop,
|
|
252
|
+
verification_program,
|
|
253
|
+
max_attempts,
|
|
254
|
+
budget,
|
|
255
|
+
auto_submit,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@cli.command()
|
|
260
|
+
@click.argument("input_prompt", type=click.Path(exists=True))
|
|
261
|
+
@click.argument("input_code", type=click.Path(exists=True))
|
|
262
|
+
@click.argument("example_code", type=click.Path(exists=True))
|
|
263
|
+
@click.option("--output-sub", type=click.Path(), help="Where to save the generated sub-prompt file.")
|
|
264
|
+
@click.option("--output-modified", type=click.Path(), help="Where to save the modified prompt file.")
|
|
265
|
+
@click.pass_context
|
|
266
|
+
@track_cost
|
|
267
|
+
def split(
|
|
268
|
+
ctx,
|
|
269
|
+
input_prompt: str,
|
|
270
|
+
input_code: str,
|
|
271
|
+
example_code: str,
|
|
272
|
+
output_sub: Optional[str],
|
|
273
|
+
output_modified: Optional[str],
|
|
274
|
+
) -> Tuple[str, str, float]:
|
|
275
|
+
"""Split large complex prompt files into smaller, more manageable prompt files."""
|
|
276
|
+
return split_main(ctx, input_prompt, input_code, example_code, output_sub, output_modified)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@cli.command()
|
|
280
|
+
@click.argument("change_prompt_file", type=click.Path(exists=True))
|
|
281
|
+
@click.argument("input_code", type=click.Path(exists=True))
|
|
282
|
+
@click.argument("input_prompt_file", type=click.Path(exists=False), required=False)
|
|
283
|
+
@click.option("--output", type=click.Path(), help="Where to save the modified prompt file.")
|
|
284
|
+
@click.option("--csv", is_flag=True, help="Use a CSV file for change prompts instead of a single text file.")
|
|
285
|
+
@click.pass_context
|
|
286
|
+
@track_cost
|
|
287
|
+
def change(
|
|
288
|
+
ctx,
|
|
289
|
+
change_prompt_file: str,
|
|
290
|
+
input_code: str,
|
|
291
|
+
input_prompt_file: Optional[str],
|
|
292
|
+
output: Optional[str],
|
|
293
|
+
csv: bool
|
|
294
|
+
) -> Tuple[str, float, str]:
|
|
295
|
+
"""Modify an input prompt file based on a change prompt and the corresponding input code."""
|
|
296
|
+
return change_main(ctx, change_prompt_file, input_code, input_prompt_file, output, csv)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
@cli.command()
|
|
300
|
+
@click.argument("input_prompt_file", type=click.Path(exists=True))
|
|
301
|
+
@click.argument("modified_code_file", type=click.Path(exists=True))
|
|
302
|
+
@click.argument("input_code_file", type=click.Path(exists=True), required=False)
|
|
303
|
+
@click.option("--output", type=click.Path(), help="Where to save the modified prompt file.")
|
|
304
|
+
@click.option(
|
|
305
|
+
"--git",
|
|
306
|
+
is_flag=True,
|
|
307
|
+
help="Use git history to find the original code file instead of providing INPUT_CODE_FILE."
|
|
308
|
+
)
|
|
309
|
+
@click.pass_context
|
|
310
|
+
@track_cost
|
|
311
|
+
def update(
|
|
312
|
+
ctx,
|
|
313
|
+
input_prompt_file: str,
|
|
314
|
+
modified_code_file: str,
|
|
315
|
+
input_code_file: Optional[str],
|
|
316
|
+
output: Optional[str],
|
|
317
|
+
git: bool,
|
|
318
|
+
) -> Tuple[str, float, str]:
|
|
319
|
+
"""Update the original prompt file based on the original code and the modified code."""
|
|
320
|
+
return update_main(ctx, input_prompt_file, modified_code_file, input_code_file, output, git)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
@cli.command()
|
|
324
|
+
@click.argument("prompt_files", nargs=-1, type=click.Path(exists=True))
|
|
325
|
+
@click.argument("change_file", type=click.Path(exists=True))
|
|
326
|
+
@click.option("--output", type=click.Path(), help="Where to save CSV analysis results.")
|
|
327
|
+
@click.pass_context
|
|
328
|
+
@track_cost
|
|
329
|
+
def detect(
|
|
330
|
+
ctx,
|
|
331
|
+
prompt_files: List[str],
|
|
332
|
+
change_file: str,
|
|
333
|
+
output: Optional[str]
|
|
334
|
+
) -> Tuple[List[dict], float, str]:
|
|
335
|
+
"""Analyze a list of prompt files and a change description to see which prompts need changes."""
|
|
336
|
+
return detect_change_main(ctx, prompt_files, change_file, output)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
@cli.command()
|
|
340
|
+
@click.argument("prompt1", type=click.Path(exists=True))
|
|
341
|
+
@click.argument("prompt2", type=click.Path(exists=True))
|
|
342
|
+
@click.option("--output", type=click.Path(), help="Where to save the conflict analysis CSV.")
|
|
343
|
+
@click.pass_context
|
|
344
|
+
@track_cost
|
|
345
|
+
def conflicts(
|
|
346
|
+
ctx,
|
|
347
|
+
prompt1: str,
|
|
348
|
+
prompt2: str,
|
|
349
|
+
output: Optional[str]
|
|
350
|
+
) -> Tuple[List[dict], float, str]:
|
|
351
|
+
"""Analyze two prompt files to find conflicts and suggest resolutions."""
|
|
352
|
+
return conflicts_main(ctx, prompt1, prompt2, output)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
@cli.command()
|
|
356
|
+
@click.argument("prompt_file", type=click.Path(exists=True))
|
|
357
|
+
@click.argument("code_file", type=click.Path(exists=True))
|
|
358
|
+
@click.argument("program_file", type=click.Path(exists=True))
|
|
359
|
+
@click.argument("error_file", type=click.Path())
|
|
360
|
+
@click.option("--output", type=click.Path(), help="Where to save the fixed code file.")
|
|
361
|
+
@click.option("--output-program", type=click.Path(), help="Where to save the fixed program file.")
|
|
362
|
+
@click.option("--loop", is_flag=True, help="Enable iterative fixing process.")
|
|
363
|
+
@click.option("--max-attempts", type=int, default=3, help="Maximum fix attempts before giving up.")
|
|
364
|
+
@click.option("--budget", type=float, default=5.0, help="Maximum cost allowed for the fixing process.")
|
|
365
|
+
@click.pass_context
|
|
366
|
+
@track_cost
|
|
367
|
+
def crash(
|
|
368
|
+
ctx,
|
|
369
|
+
prompt_file: str,
|
|
370
|
+
code_file: str,
|
|
371
|
+
program_file: str,
|
|
372
|
+
error_file: str,
|
|
373
|
+
output: Optional[str],
|
|
374
|
+
output_program: Optional[str],
|
|
375
|
+
loop: bool,
|
|
376
|
+
max_attempts: int,
|
|
377
|
+
budget: float
|
|
378
|
+
) -> Tuple[bool, str, str, int, float, str]:
|
|
379
|
+
"""Fix errors in a code module that caused a program to crash."""
|
|
380
|
+
return crash_main(
|
|
381
|
+
ctx,
|
|
382
|
+
prompt_file,
|
|
383
|
+
code_file,
|
|
384
|
+
program_file,
|
|
385
|
+
error_file,
|
|
386
|
+
output,
|
|
387
|
+
output_program,
|
|
388
|
+
loop,
|
|
389
|
+
max_attempts,
|
|
390
|
+
budget,
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
# ----------------------------------------------------------------------
|
|
394
|
+
# Simplified shell RC path logic
|
|
395
|
+
# ----------------------------------------------------------------------
|
|
396
|
+
def get_shell_rc_path(shell: str) -> Optional[str]:
|
|
397
|
+
"""Return the default RC file path for a given shell name."""
|
|
398
|
+
home = os.path.expanduser("~")
|
|
399
|
+
if shell == "bash":
|
|
400
|
+
return os.path.join(home, ".bashrc")
|
|
401
|
+
elif shell == "zsh":
|
|
402
|
+
return os.path.join(home, ".zshrc")
|
|
403
|
+
elif shell == "fish":
|
|
404
|
+
return os.path.join(home, ".config", "fish", "config.fish")
|
|
405
|
+
return None
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def get_current_shell() -> Optional[str]:
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
"""Determine the currently running shell more reliably."""
|
|
412
|
+
if not os.environ.get('PYTEST_CURRENT_TEST'):
|
|
413
|
+
# Method 1: Check process name using 'ps'
|
|
414
|
+
try:
|
|
415
|
+
import subprocess
|
|
416
|
+
result = subprocess.run(['ps', '-p', str(os.getppid()), '-o', 'comm='],
|
|
417
|
+
capture_output=True, text=True)
|
|
418
|
+
if result.returncode == 0:
|
|
419
|
+
# Strip whitespace and get basename without path
|
|
420
|
+
shell = os.path.basename(result.stdout.strip())
|
|
421
|
+
# Remove leading dash if present (login shell)
|
|
422
|
+
return shell.lstrip('-')
|
|
423
|
+
except (subprocess.SubprocessError, FileNotFoundError):
|
|
424
|
+
pass
|
|
425
|
+
|
|
426
|
+
# Method 2: Check $0 special parameter
|
|
427
|
+
try:
|
|
428
|
+
result = subprocess.run(['sh', '-c', 'echo "$0"'],
|
|
429
|
+
capture_output=True, text=True)
|
|
430
|
+
if result.returncode == 0:
|
|
431
|
+
shell = os.path.basename(result.stdout.strip())
|
|
432
|
+
return shell.lstrip('-')
|
|
433
|
+
except (subprocess.SubprocessError, FileNotFoundError):
|
|
434
|
+
pass
|
|
435
|
+
|
|
436
|
+
# Fallback to SHELL env var if all else fails
|
|
437
|
+
return os.path.basename(os.environ.get("SHELL", ""))
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def get_completion_script_extension(shell: str) -> str:
|
|
441
|
+
"""Get the appropriate file extension for shell completion scripts."""
|
|
442
|
+
mapping = {
|
|
443
|
+
"bash": "sh",
|
|
444
|
+
"zsh": "zsh",
|
|
445
|
+
"fish": "fish"
|
|
446
|
+
}
|
|
447
|
+
return mapping.get(shell, shell)
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
@cli.command(name="install_completion")
|
|
451
|
+
def install_completion():
|
|
452
|
+
"""
|
|
453
|
+
Install shell completion for the PDD CLI by detecting the user’s shell,
|
|
454
|
+
copying the relevant completion script, and appending a source command
|
|
455
|
+
to the user’s shell RC file if not already present.
|
|
456
|
+
"""
|
|
457
|
+
shell = get_current_shell()
|
|
458
|
+
rc_file = get_shell_rc_path(shell)
|
|
459
|
+
if not rc_file:
|
|
460
|
+
rprint(f"[red]Unsupported shell: {shell}[/red]")
|
|
461
|
+
raise click.Abort()
|
|
462
|
+
|
|
463
|
+
ext = get_completion_script_extension(shell)
|
|
464
|
+
|
|
465
|
+
# Dynamically look up the local path at runtime:
|
|
466
|
+
local_pdd_path = get_local_pdd_path()
|
|
467
|
+
completion_script_path = os.path.join(local_pdd_path, f"pdd_completion.{ext}")
|
|
468
|
+
|
|
469
|
+
if not os.path.exists(completion_script_path):
|
|
470
|
+
rprint(f"[red]Completion script not found: {completion_script_path}[/red]")
|
|
471
|
+
raise click.Abort()
|
|
472
|
+
|
|
473
|
+
source_command = f"source {completion_script_path}"
|
|
474
|
+
|
|
475
|
+
try:
|
|
476
|
+
# Ensure the RC file exists (create if missing).
|
|
477
|
+
if not os.path.exists(rc_file):
|
|
478
|
+
os.makedirs(os.path.dirname(rc_file), exist_ok=True)
|
|
479
|
+
with open(rc_file, "w", encoding="utf-8") as cf:
|
|
480
|
+
cf.write("")
|
|
481
|
+
|
|
482
|
+
# Read existing content
|
|
483
|
+
with open(rc_file, "r", encoding="utf-8") as cf:
|
|
484
|
+
content = cf.read()
|
|
485
|
+
|
|
486
|
+
if source_command not in content:
|
|
487
|
+
with open(rc_file, "a", encoding="utf-8") as rf:
|
|
488
|
+
rf.write(f"\n# PDD CLI completion\n{source_command}\n")
|
|
489
|
+
|
|
490
|
+
rprint(f"[green]Shell completion installed for {shell}.[/green]")
|
|
491
|
+
rprint(f"Please restart your shell or run 'source {rc_file}' to enable completion.")
|
|
492
|
+
else:
|
|
493
|
+
rprint(f"[yellow]Shell completion already installed for {shell}.[/yellow]")
|
|
494
|
+
except OSError as exc:
|
|
495
|
+
rprint(f"[red]Failed to install shell completion: {exc}[/red]")
|
|
496
|
+
raise click.Abort()
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
@cli.command()
|
|
500
|
+
@click.argument("prompt_file", type=click.Path(exists=True))
|
|
501
|
+
@click.argument("code_file", type=click.Path(exists=True))
|
|
502
|
+
@click.argument("code_line", type=int)
|
|
503
|
+
@click.option("--output", type=click.Path(), help="Where to save the trace analysis results.")
|
|
504
|
+
@click.pass_context
|
|
505
|
+
@track_cost
|
|
506
|
+
def trace(
|
|
507
|
+
ctx,
|
|
508
|
+
prompt_file: str,
|
|
509
|
+
code_file: str,
|
|
510
|
+
code_line: int,
|
|
511
|
+
output: Optional[str]
|
|
512
|
+
) -> Tuple[str, float, str]:
|
|
513
|
+
"""
|
|
514
|
+
Find the associated line number between a prompt file and the generated code.
|
|
515
|
+
"""
|
|
516
|
+
return trace_main(ctx, prompt_file, code_file, code_line, output)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
@cli.command()
|
|
520
|
+
@click.argument("prompt_file", type=click.Path(exists=True))
|
|
521
|
+
@click.argument("code_file", type=click.Path(exists=True))
|
|
522
|
+
@click.argument("program_file", type=click.Path(exists=True))
|
|
523
|
+
@click.argument("current_output", type=click.Path(exists=True))
|
|
524
|
+
@click.argument("desired_output", type=click.Path(exists=True))
|
|
525
|
+
@click.option(
|
|
526
|
+
"--output",
|
|
527
|
+
metavar="LOCATION",
|
|
528
|
+
type=click.Path(),
|
|
529
|
+
help="Where to save the bug-related unit test."
|
|
530
|
+
)
|
|
531
|
+
@click.option("--language", default="Python", help="Specify the programming language.")
|
|
532
|
+
@click.pass_context
|
|
533
|
+
@track_cost
|
|
534
|
+
def bug(
|
|
535
|
+
ctx,
|
|
536
|
+
prompt_file: str,
|
|
537
|
+
code_file: str,
|
|
538
|
+
program_file: str,
|
|
539
|
+
current_output: str,
|
|
540
|
+
desired_output: str,
|
|
541
|
+
output: Optional[str],
|
|
542
|
+
language: Optional[str]
|
|
543
|
+
) -> Tuple[str, float, str]:
|
|
544
|
+
"""
|
|
545
|
+
Generate a unit test based on observed and desired outputs for given code and prompt.
|
|
546
|
+
"""
|
|
547
|
+
return bug_main(
|
|
548
|
+
ctx,
|
|
549
|
+
prompt_file,
|
|
550
|
+
code_file,
|
|
551
|
+
program_file,
|
|
552
|
+
current_output,
|
|
553
|
+
desired_output,
|
|
554
|
+
output,
|
|
555
|
+
language
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
@cli.command()
|
|
560
|
+
@click.argument("prompt_file", type=click.Path(exists=True))
|
|
561
|
+
@click.argument("directory_path", type=str)
|
|
562
|
+
@click.option(
|
|
563
|
+
"--output",
|
|
564
|
+
type=click.Path(),
|
|
565
|
+
help="Specify where to save the modified prompt file with dependencies inserted."
|
|
566
|
+
)
|
|
567
|
+
@click.option(
|
|
568
|
+
"--csv",
|
|
569
|
+
type=click.Path(),
|
|
570
|
+
default="./project_dependencies.csv",
|
|
571
|
+
help="Specify the CSV file with dependency info."
|
|
572
|
+
)
|
|
573
|
+
@click.option(
|
|
574
|
+
"--force-scan",
|
|
575
|
+
is_flag=True,
|
|
576
|
+
help="Force rescanning of all potential dependency files."
|
|
577
|
+
)
|
|
578
|
+
@click.pass_context
|
|
579
|
+
@track_cost
|
|
580
|
+
def auto_deps(
|
|
581
|
+
ctx,
|
|
582
|
+
prompt_file: str,
|
|
583
|
+
directory_path: str,
|
|
584
|
+
output: Optional[str],
|
|
585
|
+
csv: Optional[str],
|
|
586
|
+
force_scan: bool
|
|
587
|
+
) -> Tuple[str, float, str]:
|
|
588
|
+
"""
|
|
589
|
+
Analyze a prompt file and a directory of potential dependencies,
|
|
590
|
+
inserting needed dependencies into the prompt.
|
|
591
|
+
"""
|
|
592
|
+
# Strip quotes if present
|
|
593
|
+
if directory_path.startswith('"') and directory_path.endswith('"'):
|
|
594
|
+
directory_path = directory_path[1:-1]
|
|
595
|
+
|
|
596
|
+
return auto_deps_main(
|
|
597
|
+
ctx=ctx,
|
|
598
|
+
prompt_file=prompt_file,
|
|
599
|
+
directory_path=directory_path,
|
|
600
|
+
auto_deps_csv_path=csv,
|
|
601
|
+
output=output,
|
|
602
|
+
force_scan=force_scan
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
if __name__ == "__main__":
|
|
607
|
+
cli()
|