pdd-cli 0.0.45__py3-none-any.whl → 0.0.118__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 +40 -8
- pdd/agentic_bug.py +323 -0
- pdd/agentic_bug_orchestrator.py +497 -0
- pdd/agentic_change.py +231 -0
- pdd/agentic_change_orchestrator.py +526 -0
- pdd/agentic_common.py +598 -0
- pdd/agentic_crash.py +534 -0
- pdd/agentic_e2e_fix.py +319 -0
- pdd/agentic_e2e_fix_orchestrator.py +426 -0
- pdd/agentic_fix.py +1294 -0
- pdd/agentic_langtest.py +162 -0
- pdd/agentic_update.py +387 -0
- pdd/agentic_verify.py +183 -0
- pdd/architecture_sync.py +565 -0
- pdd/auth_service.py +210 -0
- pdd/auto_deps_main.py +71 -51
- pdd/auto_include.py +245 -5
- pdd/auto_update.py +125 -47
- pdd/bug_main.py +196 -23
- pdd/bug_to_unit_test.py +2 -0
- pdd/change_main.py +11 -4
- pdd/cli.py +22 -1181
- pdd/cmd_test_main.py +350 -150
- pdd/code_generator.py +60 -18
- pdd/code_generator_main.py +790 -57
- pdd/commands/__init__.py +48 -0
- pdd/commands/analysis.py +306 -0
- pdd/commands/auth.py +309 -0
- pdd/commands/connect.py +290 -0
- pdd/commands/fix.py +163 -0
- pdd/commands/generate.py +257 -0
- pdd/commands/maintenance.py +175 -0
- pdd/commands/misc.py +87 -0
- pdd/commands/modify.py +256 -0
- pdd/commands/report.py +144 -0
- pdd/commands/sessions.py +284 -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 +589 -111
- pdd/context_generator.py +10 -2
- pdd/context_generator_main.py +175 -76
- pdd/continue_generation.py +53 -10
- pdd/core/__init__.py +33 -0
- pdd/core/cli.py +527 -0
- pdd/core/cloud.py +237 -0
- pdd/core/dump.py +554 -0
- pdd/core/errors.py +67 -0
- pdd/core/remote_session.py +61 -0
- pdd/core/utils.py +90 -0
- pdd/crash_main.py +262 -33
- pdd/data/language_format.csv +71 -63
- pdd/data/llm_model.csv +20 -18
- pdd/detect_change_main.py +5 -4
- pdd/docs/prompting_guide.md +864 -0
- pdd/docs/whitepaper_with_benchmarks/data_and_functions/benchmark_analysis.py +495 -0
- pdd/docs/whitepaper_with_benchmarks/data_and_functions/creation_compare.py +528 -0
- pdd/fix_code_loop.py +523 -95
- pdd/fix_code_module_errors.py +6 -2
- pdd/fix_error_loop.py +491 -92
- pdd/fix_errors_from_unit_tests.py +4 -3
- pdd/fix_main.py +278 -21
- pdd/fix_verification_errors.py +12 -100
- pdd/fix_verification_errors_loop.py +529 -286
- pdd/fix_verification_main.py +294 -89
- pdd/frontend/dist/assets/index-B5DZHykP.css +1 -0
- pdd/frontend/dist/assets/index-DQ3wkeQ2.js +449 -0
- pdd/frontend/dist/index.html +376 -0
- pdd/frontend/dist/logo.svg +33 -0
- pdd/generate_output_paths.py +139 -15
- pdd/generate_test.py +218 -146
- pdd/get_comment.py +19 -44
- pdd/get_extension.py +8 -9
- pdd/get_jwt_token.py +318 -22
- pdd/get_language.py +8 -7
- pdd/get_run_command.py +75 -0
- pdd/get_test_command.py +68 -0
- pdd/git_update.py +70 -19
- pdd/incremental_code_generator.py +2 -2
- pdd/insert_includes.py +13 -4
- pdd/llm_invoke.py +1711 -181
- pdd/load_prompt_template.py +19 -12
- pdd/path_resolution.py +140 -0
- pdd/pdd_completion.fish +25 -2
- pdd/pdd_completion.sh +30 -4
- pdd/pdd_completion.zsh +79 -4
- pdd/postprocess.py +14 -4
- pdd/preprocess.py +293 -24
- pdd/preprocess_main.py +41 -6
- pdd/prompts/agentic_bug_step10_pr_LLM.prompt +182 -0
- pdd/prompts/agentic_bug_step1_duplicate_LLM.prompt +73 -0
- pdd/prompts/agentic_bug_step2_docs_LLM.prompt +129 -0
- pdd/prompts/agentic_bug_step3_triage_LLM.prompt +95 -0
- pdd/prompts/agentic_bug_step4_reproduce_LLM.prompt +97 -0
- pdd/prompts/agentic_bug_step5_root_cause_LLM.prompt +123 -0
- pdd/prompts/agentic_bug_step6_test_plan_LLM.prompt +107 -0
- pdd/prompts/agentic_bug_step7_generate_LLM.prompt +172 -0
- pdd/prompts/agentic_bug_step8_verify_LLM.prompt +119 -0
- pdd/prompts/agentic_bug_step9_e2e_test_LLM.prompt +289 -0
- pdd/prompts/agentic_change_step10_identify_issues_LLM.prompt +1006 -0
- pdd/prompts/agentic_change_step11_fix_issues_LLM.prompt +984 -0
- pdd/prompts/agentic_change_step12_create_pr_LLM.prompt +131 -0
- pdd/prompts/agentic_change_step1_duplicate_LLM.prompt +73 -0
- pdd/prompts/agentic_change_step2_docs_LLM.prompt +101 -0
- pdd/prompts/agentic_change_step3_research_LLM.prompt +126 -0
- pdd/prompts/agentic_change_step4_clarify_LLM.prompt +164 -0
- pdd/prompts/agentic_change_step5_docs_change_LLM.prompt +981 -0
- pdd/prompts/agentic_change_step6_devunits_LLM.prompt +1005 -0
- pdd/prompts/agentic_change_step7_architecture_LLM.prompt +1044 -0
- pdd/prompts/agentic_change_step8_analyze_LLM.prompt +1027 -0
- pdd/prompts/agentic_change_step9_implement_LLM.prompt +1077 -0
- pdd/prompts/agentic_crash_explore_LLM.prompt +49 -0
- pdd/prompts/agentic_e2e_fix_step1_unit_tests_LLM.prompt +90 -0
- pdd/prompts/agentic_e2e_fix_step2_e2e_tests_LLM.prompt +91 -0
- pdd/prompts/agentic_e2e_fix_step3_root_cause_LLM.prompt +89 -0
- pdd/prompts/agentic_e2e_fix_step4_fix_e2e_tests_LLM.prompt +96 -0
- pdd/prompts/agentic_e2e_fix_step5_identify_devunits_LLM.prompt +91 -0
- pdd/prompts/agentic_e2e_fix_step6_create_unit_tests_LLM.prompt +106 -0
- pdd/prompts/agentic_e2e_fix_step7_verify_tests_LLM.prompt +116 -0
- pdd/prompts/agentic_e2e_fix_step8_run_pdd_fix_LLM.prompt +120 -0
- pdd/prompts/agentic_e2e_fix_step9_verify_all_LLM.prompt +146 -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 +925 -0
- pdd/prompts/agentic_verify_explore_LLM.prompt +45 -0
- pdd/prompts/auto_include_LLM.prompt +122 -905
- pdd/prompts/change_LLM.prompt +3093 -1
- pdd/prompts/detect_change_LLM.prompt +686 -27
- pdd/prompts/example_generator_LLM.prompt +22 -1
- pdd/prompts/extract_code_LLM.prompt +5 -1
- pdd/prompts/extract_program_code_fix_LLM.prompt +7 -1
- pdd/prompts/extract_prompt_update_LLM.prompt +7 -8
- pdd/prompts/extract_promptline_LLM.prompt +17 -11
- pdd/prompts/find_verification_errors_LLM.prompt +6 -0
- pdd/prompts/fix_code_module_errors_LLM.prompt +12 -2
- pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +9 -0
- pdd/prompts/fix_verification_errors_LLM.prompt +22 -0
- pdd/prompts/generate_test_LLM.prompt +41 -7
- pdd/prompts/generate_test_from_example_LLM.prompt +115 -0
- pdd/prompts/increase_tests_LLM.prompt +1 -5
- pdd/prompts/insert_includes_LLM.prompt +316 -186
- pdd/prompts/prompt_code_diff_LLM.prompt +119 -0
- pdd/prompts/prompt_diff_LLM.prompt +82 -0
- pdd/prompts/trace_LLM.prompt +25 -22
- pdd/prompts/unfinished_prompt_LLM.prompt +85 -1
- pdd/prompts/update_prompt_LLM.prompt +22 -1
- pdd/pytest_output.py +127 -12
- pdd/remote_session.py +876 -0
- pdd/render_mermaid.py +236 -0
- pdd/server/__init__.py +52 -0
- pdd/server/app.py +335 -0
- pdd/server/click_executor.py +587 -0
- pdd/server/executor.py +338 -0
- pdd/server/jobs.py +661 -0
- pdd/server/models.py +241 -0
- pdd/server/routes/__init__.py +31 -0
- pdd/server/routes/architecture.py +451 -0
- pdd/server/routes/auth.py +364 -0
- pdd/server/routes/commands.py +929 -0
- pdd/server/routes/config.py +42 -0
- pdd/server/routes/files.py +603 -0
- pdd/server/routes/prompts.py +1322 -0
- pdd/server/routes/websocket.py +473 -0
- pdd/server/security.py +243 -0
- pdd/server/terminal_spawner.py +209 -0
- pdd/server/token_counter.py +222 -0
- pdd/setup_tool.py +648 -0
- pdd/simple_math.py +2 -0
- pdd/split_main.py +3 -2
- pdd/summarize_directory.py +237 -195
- pdd/sync_animation.py +8 -4
- pdd/sync_determine_operation.py +839 -112
- pdd/sync_main.py +351 -57
- pdd/sync_orchestration.py +1400 -756
- pdd/sync_tui.py +848 -0
- pdd/template_expander.py +161 -0
- pdd/template_registry.py +264 -0
- pdd/templates/architecture/architecture_json.prompt +237 -0
- pdd/templates/generic/generate_prompt.prompt +174 -0
- pdd/trace.py +168 -12
- pdd/trace_main.py +4 -3
- pdd/track_cost.py +140 -63
- pdd/unfinished_prompt.py +51 -4
- pdd/update_main.py +567 -67
- pdd/update_model_costs.py +2 -2
- pdd/update_prompt.py +19 -4
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/METADATA +29 -11
- pdd_cli-0.0.118.dist-info/RECORD +227 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/licenses/LICENSE +1 -1
- pdd_cli-0.0.45.dist-info/RECORD +0 -116
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/top_level.txt +0 -0
pdd/commands/generate.py
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generate, test, and example commands.
|
|
3
|
+
"""
|
|
4
|
+
import click
|
|
5
|
+
from typing import Dict, Optional, Tuple, List
|
|
6
|
+
|
|
7
|
+
from ..code_generator_main import code_generator_main
|
|
8
|
+
from ..context_generator_main import context_generator_main
|
|
9
|
+
from ..cmd_test_main import cmd_test_main
|
|
10
|
+
from ..track_cost import track_cost
|
|
11
|
+
from ..core.errors import handle_error, console
|
|
12
|
+
|
|
13
|
+
class GenerateCommand(click.Command):
|
|
14
|
+
"""Ensure help shows PROMPT_FILE as required even when validated at runtime."""
|
|
15
|
+
|
|
16
|
+
def collect_usage_pieces(self, ctx: click.Context) -> List[str]:
|
|
17
|
+
pieces = super().collect_usage_pieces(ctx)
|
|
18
|
+
return ["PROMPT_FILE" if piece == "[PROMPT_FILE]" else piece for piece in pieces]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@click.command("generate", cls=GenerateCommand)
|
|
22
|
+
@click.argument("prompt_file", required=False, type=click.Path(exists=True, dir_okay=False))
|
|
23
|
+
@click.option(
|
|
24
|
+
"--output",
|
|
25
|
+
type=click.Path(writable=True),
|
|
26
|
+
default=None,
|
|
27
|
+
help="Specify where to save the generated code (file or directory).",
|
|
28
|
+
)
|
|
29
|
+
@click.option(
|
|
30
|
+
"--original-prompt",
|
|
31
|
+
"original_prompt_file_path",
|
|
32
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
33
|
+
default=None,
|
|
34
|
+
help="Path to the original prompt file for incremental generation.",
|
|
35
|
+
)
|
|
36
|
+
@click.option(
|
|
37
|
+
"--incremental",
|
|
38
|
+
"incremental_flag",
|
|
39
|
+
is_flag=True,
|
|
40
|
+
default=False,
|
|
41
|
+
help="Force incremental patching even if changes are significant (requires existing output).",
|
|
42
|
+
)
|
|
43
|
+
@click.option(
|
|
44
|
+
"-e",
|
|
45
|
+
"--env",
|
|
46
|
+
"env_kv",
|
|
47
|
+
multiple=True,
|
|
48
|
+
help="Set template variable (KEY=VALUE) or read KEY from env",
|
|
49
|
+
)
|
|
50
|
+
@click.option(
|
|
51
|
+
"--template",
|
|
52
|
+
"template_name",
|
|
53
|
+
type=str,
|
|
54
|
+
default=None,
|
|
55
|
+
help="Use a packaged/project template by name (e.g., architecture/architecture_json)",
|
|
56
|
+
)
|
|
57
|
+
@click.option(
|
|
58
|
+
"--unit-test",
|
|
59
|
+
"unit_test_file",
|
|
60
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
61
|
+
default=None,
|
|
62
|
+
help="Path to a unit test file to include in the prompt.",
|
|
63
|
+
)
|
|
64
|
+
@click.option(
|
|
65
|
+
"--exclude-tests",
|
|
66
|
+
"exclude_tests",
|
|
67
|
+
is_flag=True,
|
|
68
|
+
default=False,
|
|
69
|
+
help="Do not automatically include test files found in the default tests directory.",
|
|
70
|
+
)
|
|
71
|
+
@click.pass_context
|
|
72
|
+
@track_cost
|
|
73
|
+
def generate(
|
|
74
|
+
ctx: click.Context,
|
|
75
|
+
prompt_file: Optional[str],
|
|
76
|
+
output: Optional[str],
|
|
77
|
+
original_prompt_file_path: Optional[str],
|
|
78
|
+
incremental_flag: bool,
|
|
79
|
+
env_kv: Tuple[str, ...],
|
|
80
|
+
template_name: Optional[str],
|
|
81
|
+
unit_test_file: Optional[str],
|
|
82
|
+
exclude_tests: bool,
|
|
83
|
+
) -> Optional[Tuple[str, float, str]]:
|
|
84
|
+
"""
|
|
85
|
+
Generate code from a prompt file.
|
|
86
|
+
|
|
87
|
+
\b
|
|
88
|
+
Related commands:
|
|
89
|
+
test Generate unit tests for a prompt.
|
|
90
|
+
example Generate example code for a prompt.
|
|
91
|
+
|
|
92
|
+
\b
|
|
93
|
+
Note:
|
|
94
|
+
Global options (for example ``--force``, ``--temperature``, ``--time``)
|
|
95
|
+
can be placed either before or after the subcommand. For example:
|
|
96
|
+
|
|
97
|
+
pdd generate my.prompt --force --temperature 0.5
|
|
98
|
+
"""
|
|
99
|
+
try:
|
|
100
|
+
# Resolve template to a prompt path when requested
|
|
101
|
+
if template_name and prompt_file:
|
|
102
|
+
raise click.UsageError("Provide either --template or a PROMPT_FILE path, not both.")
|
|
103
|
+
if template_name:
|
|
104
|
+
try:
|
|
105
|
+
from .. import template_registry as _tpl
|
|
106
|
+
meta = _tpl.load_template(template_name)
|
|
107
|
+
prompt_file = meta.get("path")
|
|
108
|
+
if not prompt_file:
|
|
109
|
+
raise click.UsageError(f"Template '{template_name}' did not return a valid path")
|
|
110
|
+
except Exception as e:
|
|
111
|
+
raise click.UsageError(f"Failed to load template '{template_name}': {e}")
|
|
112
|
+
if not template_name and not prompt_file:
|
|
113
|
+
raise click.UsageError("Missing PROMPT_FILE. To use a template, pass --template NAME instead.")
|
|
114
|
+
# Parse -e/--env arguments into a dict
|
|
115
|
+
env_vars: Dict[str, str] = {}
|
|
116
|
+
import os as _os
|
|
117
|
+
for item in env_kv or ():
|
|
118
|
+
if "=" in item:
|
|
119
|
+
key, value = item.split("=", 1)
|
|
120
|
+
key = key.strip()
|
|
121
|
+
if key:
|
|
122
|
+
env_vars[key] = value
|
|
123
|
+
else:
|
|
124
|
+
key = item.strip()
|
|
125
|
+
if key:
|
|
126
|
+
val = _os.environ.get(key)
|
|
127
|
+
if val is not None:
|
|
128
|
+
env_vars[key] = val
|
|
129
|
+
else:
|
|
130
|
+
if ctx.obj.get("verbose") and not ctx.obj.get("quiet"):
|
|
131
|
+
console.print(f"[warning]-e {key} not found in environment; skipping[/warning]")
|
|
132
|
+
generated_code, incremental, total_cost, model_name = code_generator_main(
|
|
133
|
+
ctx=ctx,
|
|
134
|
+
prompt_file=prompt_file, # resolved template path or user path
|
|
135
|
+
output=output,
|
|
136
|
+
original_prompt_file_path=original_prompt_file_path,
|
|
137
|
+
force_incremental_flag=incremental_flag,
|
|
138
|
+
env_vars=env_vars or None,
|
|
139
|
+
unit_test_file=unit_test_file,
|
|
140
|
+
exclude_tests=exclude_tests,
|
|
141
|
+
)
|
|
142
|
+
return generated_code, total_cost, model_name
|
|
143
|
+
except click.Abort:
|
|
144
|
+
# Let user cancellation (e.g., pressing 'no' on overwrite prompt) propagate
|
|
145
|
+
# to PDDCLI.invoke() for graceful handling (fix for issue #186)
|
|
146
|
+
raise
|
|
147
|
+
except Exception as exception:
|
|
148
|
+
handle_error(exception, "generate", ctx.obj.get("quiet", False))
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@click.command("example")
|
|
153
|
+
@click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False))
|
|
154
|
+
@click.argument("code_file", type=click.Path(exists=True, dir_okay=False))
|
|
155
|
+
@click.option(
|
|
156
|
+
"--output",
|
|
157
|
+
type=click.Path(writable=True),
|
|
158
|
+
default=None,
|
|
159
|
+
help="Specify where to save the generated example code (file or directory).",
|
|
160
|
+
)
|
|
161
|
+
@click.pass_context
|
|
162
|
+
@track_cost
|
|
163
|
+
def example(
|
|
164
|
+
ctx: click.Context,
|
|
165
|
+
prompt_file: str,
|
|
166
|
+
code_file: str,
|
|
167
|
+
output: Optional[str]
|
|
168
|
+
) -> Optional[Tuple[str, float, str]]:
|
|
169
|
+
"""Generate example code for a given prompt and implementation."""
|
|
170
|
+
try:
|
|
171
|
+
example_code, total_cost, model_name = context_generator_main(
|
|
172
|
+
ctx=ctx,
|
|
173
|
+
prompt_file=prompt_file,
|
|
174
|
+
code_file=code_file,
|
|
175
|
+
output=output,
|
|
176
|
+
)
|
|
177
|
+
return example_code, total_cost, model_name
|
|
178
|
+
except click.Abort:
|
|
179
|
+
raise
|
|
180
|
+
except Exception as exception:
|
|
181
|
+
handle_error(exception, "example", ctx.obj.get("quiet", False))
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@click.command("test")
|
|
186
|
+
@click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False))
|
|
187
|
+
@click.argument("code_file", type=click.Path(exists=True, dir_okay=False))
|
|
188
|
+
@click.option(
|
|
189
|
+
"--output",
|
|
190
|
+
type=click.Path(writable=True),
|
|
191
|
+
default=None,
|
|
192
|
+
help="Specify where to save the generated test file (file or directory).",
|
|
193
|
+
)
|
|
194
|
+
@click.option(
|
|
195
|
+
"--language",
|
|
196
|
+
type=str,
|
|
197
|
+
default=None,
|
|
198
|
+
help="Specify the programming language."
|
|
199
|
+
)
|
|
200
|
+
@click.option(
|
|
201
|
+
"--coverage-report",
|
|
202
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
203
|
+
default=None,
|
|
204
|
+
help="Path to the coverage report file for existing tests.",
|
|
205
|
+
)
|
|
206
|
+
@click.option(
|
|
207
|
+
"--existing-tests",
|
|
208
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
209
|
+
multiple=True,
|
|
210
|
+
help="Path to existing unit test file(s). Can be specified multiple times.",
|
|
211
|
+
)
|
|
212
|
+
@click.option(
|
|
213
|
+
"--target-coverage",
|
|
214
|
+
type=click.FloatRange(0.0, 100.0),
|
|
215
|
+
default=None, # Use None, default handled in cmd_test_main or env var
|
|
216
|
+
help="Desired code coverage percentage (default: 10.0 or PDD_TEST_COVERAGE_TARGET).",
|
|
217
|
+
)
|
|
218
|
+
@click.option(
|
|
219
|
+
"--merge",
|
|
220
|
+
is_flag=True,
|
|
221
|
+
default=False,
|
|
222
|
+
help="Merge new tests with existing test file instead of creating a separate file.",
|
|
223
|
+
)
|
|
224
|
+
@click.pass_context
|
|
225
|
+
@track_cost
|
|
226
|
+
def test(
|
|
227
|
+
ctx: click.Context,
|
|
228
|
+
prompt_file: str,
|
|
229
|
+
code_file: str,
|
|
230
|
+
output: Optional[str],
|
|
231
|
+
language: Optional[str],
|
|
232
|
+
coverage_report: Optional[str],
|
|
233
|
+
existing_tests: Tuple[str, ...],
|
|
234
|
+
target_coverage: Optional[float],
|
|
235
|
+
merge: bool,
|
|
236
|
+
) -> Optional[Tuple[str, float, str]]:
|
|
237
|
+
"""Generate unit tests for a given prompt and implementation."""
|
|
238
|
+
try:
|
|
239
|
+
# Convert empty tuple to None for cmd_test_main compatibility
|
|
240
|
+
existing_tests_list = list(existing_tests) if existing_tests else None
|
|
241
|
+
test_code, total_cost, model_name = cmd_test_main(
|
|
242
|
+
ctx=ctx,
|
|
243
|
+
prompt_file=prompt_file,
|
|
244
|
+
code_file=code_file,
|
|
245
|
+
output=output,
|
|
246
|
+
language=language,
|
|
247
|
+
coverage_report=coverage_report,
|
|
248
|
+
existing_tests=existing_tests_list,
|
|
249
|
+
target_coverage=target_coverage,
|
|
250
|
+
merge=merge,
|
|
251
|
+
)
|
|
252
|
+
return test_code, total_cost, model_name
|
|
253
|
+
except click.Abort:
|
|
254
|
+
raise
|
|
255
|
+
except Exception as exception:
|
|
256
|
+
handle_error(exception, "test", ctx.obj.get("quiet", False))
|
|
257
|
+
return None
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Maintenance commands (sync, auto_deps, setup).
|
|
3
|
+
"""
|
|
4
|
+
import click
|
|
5
|
+
from typing import Optional, Tuple
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from ..sync_main import sync_main
|
|
9
|
+
from ..auto_deps_main import auto_deps_main
|
|
10
|
+
from ..track_cost import track_cost
|
|
11
|
+
from ..core.errors import handle_error
|
|
12
|
+
from ..core.utils import _run_setup_utility
|
|
13
|
+
|
|
14
|
+
@click.command("sync")
|
|
15
|
+
@click.argument("basename", required=True)
|
|
16
|
+
@click.option(
|
|
17
|
+
"--max-attempts",
|
|
18
|
+
type=int,
|
|
19
|
+
default=None,
|
|
20
|
+
help="Maximum number of fix attempts. Default: 3 or .pddrc value.",
|
|
21
|
+
)
|
|
22
|
+
@click.option(
|
|
23
|
+
"--budget",
|
|
24
|
+
type=float,
|
|
25
|
+
default=None,
|
|
26
|
+
help="Maximum total cost for the sync process. Default: 20.0 or .pddrc value.",
|
|
27
|
+
)
|
|
28
|
+
@click.option(
|
|
29
|
+
"--skip-verify",
|
|
30
|
+
is_flag=True,
|
|
31
|
+
default=False,
|
|
32
|
+
help="Skip the functional verification step.",
|
|
33
|
+
)
|
|
34
|
+
@click.option(
|
|
35
|
+
"--skip-tests",
|
|
36
|
+
is_flag=True,
|
|
37
|
+
default=False,
|
|
38
|
+
help="Skip unit test generation and fixing.",
|
|
39
|
+
)
|
|
40
|
+
@click.option(
|
|
41
|
+
"--target-coverage",
|
|
42
|
+
type=float,
|
|
43
|
+
default=None,
|
|
44
|
+
help="Desired code coverage percentage. Default: 90.0 or .pddrc value.",
|
|
45
|
+
)
|
|
46
|
+
@click.option(
|
|
47
|
+
"--dry-run",
|
|
48
|
+
is_flag=True,
|
|
49
|
+
default=False,
|
|
50
|
+
help="Analyze sync state without executing operations. Shows what sync would do.",
|
|
51
|
+
)
|
|
52
|
+
@click.option(
|
|
53
|
+
"--log",
|
|
54
|
+
is_flag=True,
|
|
55
|
+
default=False,
|
|
56
|
+
hidden=True,
|
|
57
|
+
help="Deprecated: Use --dry-run instead.",
|
|
58
|
+
)
|
|
59
|
+
@click.pass_context
|
|
60
|
+
@track_cost
|
|
61
|
+
def sync(
|
|
62
|
+
ctx: click.Context,
|
|
63
|
+
basename: str,
|
|
64
|
+
max_attempts: Optional[int],
|
|
65
|
+
budget: Optional[float],
|
|
66
|
+
skip_verify: bool,
|
|
67
|
+
skip_tests: bool,
|
|
68
|
+
target_coverage: Optional[float],
|
|
69
|
+
dry_run: bool,
|
|
70
|
+
log: bool,
|
|
71
|
+
) -> Optional[Tuple[str, float, str]]:
|
|
72
|
+
"""
|
|
73
|
+
Synchronize prompts with code and tests.
|
|
74
|
+
|
|
75
|
+
BASENAME is the base name of the prompt file (e.g., 'my_module' for 'prompts/my_module_python.prompt').
|
|
76
|
+
"""
|
|
77
|
+
# Handle deprecated --log flag
|
|
78
|
+
if log:
|
|
79
|
+
click.echo(
|
|
80
|
+
click.style(
|
|
81
|
+
"Warning: --log is deprecated, use --dry-run instead.",
|
|
82
|
+
fg="yellow"
|
|
83
|
+
),
|
|
84
|
+
err=True
|
|
85
|
+
)
|
|
86
|
+
dry_run = True
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
result, total_cost, model_name = sync_main(
|
|
90
|
+
ctx=ctx,
|
|
91
|
+
basename=basename,
|
|
92
|
+
max_attempts=max_attempts,
|
|
93
|
+
budget=budget,
|
|
94
|
+
skip_verify=skip_verify,
|
|
95
|
+
skip_tests=skip_tests,
|
|
96
|
+
target_coverage=target_coverage,
|
|
97
|
+
dry_run=dry_run,
|
|
98
|
+
)
|
|
99
|
+
return str(result), total_cost, model_name
|
|
100
|
+
except click.Abort:
|
|
101
|
+
raise
|
|
102
|
+
except Exception as exception:
|
|
103
|
+
handle_error(exception, "sync", ctx.obj.get("quiet", False))
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@click.command("auto-deps")
|
|
108
|
+
@click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False))
|
|
109
|
+
# exists=False to allow manual handling of quoted paths or paths with globs that shell didn't expand
|
|
110
|
+
@click.argument("directory_path", type=click.Path(exists=False, file_okay=False))
|
|
111
|
+
@click.option(
|
|
112
|
+
"--output",
|
|
113
|
+
type=click.Path(writable=True),
|
|
114
|
+
default=None,
|
|
115
|
+
help="Specify where to save the modified prompt (file or directory).",
|
|
116
|
+
)
|
|
117
|
+
@click.option(
|
|
118
|
+
"--csv",
|
|
119
|
+
type=click.Path(writable=True),
|
|
120
|
+
default=None,
|
|
121
|
+
help="Specify the CSV file that contains or will contain dependency information.",
|
|
122
|
+
)
|
|
123
|
+
@click.option(
|
|
124
|
+
"--force-scan",
|
|
125
|
+
is_flag=True,
|
|
126
|
+
default=False,
|
|
127
|
+
help="Force rescanning of all potential dependency files even if they exist in the CSV file.",
|
|
128
|
+
)
|
|
129
|
+
@click.pass_context
|
|
130
|
+
@track_cost
|
|
131
|
+
def auto_deps(
|
|
132
|
+
ctx: click.Context,
|
|
133
|
+
prompt_file: str,
|
|
134
|
+
directory_path: str,
|
|
135
|
+
output: Optional[str],
|
|
136
|
+
csv: Optional[str],
|
|
137
|
+
force_scan: bool,
|
|
138
|
+
) -> Optional[Tuple[str, float, str]]:
|
|
139
|
+
"""Analyze project dependencies and update the prompt file."""
|
|
140
|
+
try:
|
|
141
|
+
# Strip quotes from directory_path if present (e.g. passed incorrectly)
|
|
142
|
+
if directory_path:
|
|
143
|
+
directory_path = directory_path.strip('"').strip("'")
|
|
144
|
+
|
|
145
|
+
# auto_deps_main signature: (ctx, prompt_file, directory_path, auto_deps_csv_path, output, force_scan)
|
|
146
|
+
result, total_cost, model_name = auto_deps_main(
|
|
147
|
+
ctx=ctx,
|
|
148
|
+
prompt_file=prompt_file,
|
|
149
|
+
directory_path=directory_path,
|
|
150
|
+
auto_deps_csv_path=csv,
|
|
151
|
+
output=output,
|
|
152
|
+
force_scan=force_scan
|
|
153
|
+
)
|
|
154
|
+
return result, total_cost, model_name
|
|
155
|
+
except click.Abort:
|
|
156
|
+
raise
|
|
157
|
+
except Exception as exception:
|
|
158
|
+
handle_error(exception, "auto-deps", ctx.obj.get("quiet", False))
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@click.command("setup")
|
|
163
|
+
@click.pass_context
|
|
164
|
+
def setup(ctx: click.Context):
|
|
165
|
+
"""Run the interactive setup utility."""
|
|
166
|
+
try:
|
|
167
|
+
# Import here to allow proper mocking
|
|
168
|
+
from .. import cli as cli_module
|
|
169
|
+
quiet = ctx.obj.get("quiet", False) if ctx.obj else False
|
|
170
|
+
# First install completion
|
|
171
|
+
cli_module.install_completion(quiet=quiet)
|
|
172
|
+
# Then run setup utility
|
|
173
|
+
_run_setup_utility()
|
|
174
|
+
except Exception as e:
|
|
175
|
+
handle_error(e, "setup", False)
|
pdd/commands/misc.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Miscellaneous commands (preprocess).
|
|
3
|
+
"""
|
|
4
|
+
import click
|
|
5
|
+
from typing import Optional, Tuple
|
|
6
|
+
|
|
7
|
+
from ..preprocess_main import preprocess_main
|
|
8
|
+
from ..core.errors import handle_error
|
|
9
|
+
|
|
10
|
+
@click.command("preprocess")
|
|
11
|
+
@click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False))
|
|
12
|
+
@click.option(
|
|
13
|
+
"--output",
|
|
14
|
+
type=click.Path(writable=True),
|
|
15
|
+
default=None,
|
|
16
|
+
help="Specify where to save the preprocessed prompt file (file or directory).",
|
|
17
|
+
)
|
|
18
|
+
@click.option(
|
|
19
|
+
"--xml",
|
|
20
|
+
is_flag=True,
|
|
21
|
+
default=False,
|
|
22
|
+
help="Insert XML delimiters for structure (minimal preprocessing).",
|
|
23
|
+
)
|
|
24
|
+
@click.option(
|
|
25
|
+
"--recursive",
|
|
26
|
+
is_flag=True,
|
|
27
|
+
default=False,
|
|
28
|
+
help="Recursively preprocess includes.",
|
|
29
|
+
)
|
|
30
|
+
@click.option(
|
|
31
|
+
"--double",
|
|
32
|
+
is_flag=True,
|
|
33
|
+
default=False,
|
|
34
|
+
help="Double curly brackets.",
|
|
35
|
+
)
|
|
36
|
+
@click.option(
|
|
37
|
+
"--exclude",
|
|
38
|
+
multiple=True,
|
|
39
|
+
default=None,
|
|
40
|
+
help="List of keys to exclude from curly bracket doubling.",
|
|
41
|
+
)
|
|
42
|
+
@click.option(
|
|
43
|
+
"--pdd-tags",
|
|
44
|
+
is_flag=True,
|
|
45
|
+
default=False,
|
|
46
|
+
help="Inject PDD metadata tags (<pdd-reason>, <pdd-interface>, <pdd-dependency>) from architecture.json.",
|
|
47
|
+
)
|
|
48
|
+
@click.pass_context
|
|
49
|
+
# No @track_cost as preprocessing is local, but return dummy tuple for callback
|
|
50
|
+
def preprocess(
|
|
51
|
+
ctx: click.Context,
|
|
52
|
+
prompt_file: str,
|
|
53
|
+
output: Optional[str],
|
|
54
|
+
xml: bool,
|
|
55
|
+
recursive: bool,
|
|
56
|
+
double: bool,
|
|
57
|
+
exclude: Optional[Tuple[str, ...]],
|
|
58
|
+
pdd_tags: bool,
|
|
59
|
+
) -> Optional[Tuple[str, float, str]]:
|
|
60
|
+
"""Preprocess a prompt file to prepare it for LLM use."""
|
|
61
|
+
try:
|
|
62
|
+
# Since preprocess is a local operation, we don't track cost
|
|
63
|
+
# But we need to return a tuple in the expected format for result callback
|
|
64
|
+
result = preprocess_main(
|
|
65
|
+
ctx=ctx,
|
|
66
|
+
prompt_file=prompt_file,
|
|
67
|
+
output=output,
|
|
68
|
+
xml=xml,
|
|
69
|
+
recursive=recursive,
|
|
70
|
+
double=double,
|
|
71
|
+
exclude=list(exclude) if exclude else [],
|
|
72
|
+
pdd_tags=pdd_tags,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Handle the result from preprocess_main
|
|
76
|
+
if result is None:
|
|
77
|
+
# If preprocess_main returns None, still return a dummy tuple for the callback
|
|
78
|
+
return "", 0.0, "local"
|
|
79
|
+
else:
|
|
80
|
+
# Unpack the return value from preprocess_main
|
|
81
|
+
processed_prompt, total_cost, model_name = result
|
|
82
|
+
return processed_prompt, total_cost, model_name
|
|
83
|
+
except click.Abort:
|
|
84
|
+
raise
|
|
85
|
+
except Exception as exception:
|
|
86
|
+
handle_error(exception, "preprocess", ctx.obj.get("quiet", False))
|
|
87
|
+
return None
|