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.
Files changed (195) hide show
  1. pdd/__init__.py +40 -8
  2. pdd/agentic_bug.py +323 -0
  3. pdd/agentic_bug_orchestrator.py +497 -0
  4. pdd/agentic_change.py +231 -0
  5. pdd/agentic_change_orchestrator.py +526 -0
  6. pdd/agentic_common.py +598 -0
  7. pdd/agentic_crash.py +534 -0
  8. pdd/agentic_e2e_fix.py +319 -0
  9. pdd/agentic_e2e_fix_orchestrator.py +426 -0
  10. pdd/agentic_fix.py +1294 -0
  11. pdd/agentic_langtest.py +162 -0
  12. pdd/agentic_update.py +387 -0
  13. pdd/agentic_verify.py +183 -0
  14. pdd/architecture_sync.py +565 -0
  15. pdd/auth_service.py +210 -0
  16. pdd/auto_deps_main.py +71 -51
  17. pdd/auto_include.py +245 -5
  18. pdd/auto_update.py +125 -47
  19. pdd/bug_main.py +196 -23
  20. pdd/bug_to_unit_test.py +2 -0
  21. pdd/change_main.py +11 -4
  22. pdd/cli.py +22 -1181
  23. pdd/cmd_test_main.py +350 -150
  24. pdd/code_generator.py +60 -18
  25. pdd/code_generator_main.py +790 -57
  26. pdd/commands/__init__.py +48 -0
  27. pdd/commands/analysis.py +306 -0
  28. pdd/commands/auth.py +309 -0
  29. pdd/commands/connect.py +290 -0
  30. pdd/commands/fix.py +163 -0
  31. pdd/commands/generate.py +257 -0
  32. pdd/commands/maintenance.py +175 -0
  33. pdd/commands/misc.py +87 -0
  34. pdd/commands/modify.py +256 -0
  35. pdd/commands/report.py +144 -0
  36. pdd/commands/sessions.py +284 -0
  37. pdd/commands/templates.py +215 -0
  38. pdd/commands/utility.py +110 -0
  39. pdd/config_resolution.py +58 -0
  40. pdd/conflicts_main.py +8 -3
  41. pdd/construct_paths.py +589 -111
  42. pdd/context_generator.py +10 -2
  43. pdd/context_generator_main.py +175 -76
  44. pdd/continue_generation.py +53 -10
  45. pdd/core/__init__.py +33 -0
  46. pdd/core/cli.py +527 -0
  47. pdd/core/cloud.py +237 -0
  48. pdd/core/dump.py +554 -0
  49. pdd/core/errors.py +67 -0
  50. pdd/core/remote_session.py +61 -0
  51. pdd/core/utils.py +90 -0
  52. pdd/crash_main.py +262 -33
  53. pdd/data/language_format.csv +71 -63
  54. pdd/data/llm_model.csv +20 -18
  55. pdd/detect_change_main.py +5 -4
  56. pdd/docs/prompting_guide.md +864 -0
  57. pdd/docs/whitepaper_with_benchmarks/data_and_functions/benchmark_analysis.py +495 -0
  58. pdd/docs/whitepaper_with_benchmarks/data_and_functions/creation_compare.py +528 -0
  59. pdd/fix_code_loop.py +523 -95
  60. pdd/fix_code_module_errors.py +6 -2
  61. pdd/fix_error_loop.py +491 -92
  62. pdd/fix_errors_from_unit_tests.py +4 -3
  63. pdd/fix_main.py +278 -21
  64. pdd/fix_verification_errors.py +12 -100
  65. pdd/fix_verification_errors_loop.py +529 -286
  66. pdd/fix_verification_main.py +294 -89
  67. pdd/frontend/dist/assets/index-B5DZHykP.css +1 -0
  68. pdd/frontend/dist/assets/index-DQ3wkeQ2.js +449 -0
  69. pdd/frontend/dist/index.html +376 -0
  70. pdd/frontend/dist/logo.svg +33 -0
  71. pdd/generate_output_paths.py +139 -15
  72. pdd/generate_test.py +218 -146
  73. pdd/get_comment.py +19 -44
  74. pdd/get_extension.py +8 -9
  75. pdd/get_jwt_token.py +318 -22
  76. pdd/get_language.py +8 -7
  77. pdd/get_run_command.py +75 -0
  78. pdd/get_test_command.py +68 -0
  79. pdd/git_update.py +70 -19
  80. pdd/incremental_code_generator.py +2 -2
  81. pdd/insert_includes.py +13 -4
  82. pdd/llm_invoke.py +1711 -181
  83. pdd/load_prompt_template.py +19 -12
  84. pdd/path_resolution.py +140 -0
  85. pdd/pdd_completion.fish +25 -2
  86. pdd/pdd_completion.sh +30 -4
  87. pdd/pdd_completion.zsh +79 -4
  88. pdd/postprocess.py +14 -4
  89. pdd/preprocess.py +293 -24
  90. pdd/preprocess_main.py +41 -6
  91. pdd/prompts/agentic_bug_step10_pr_LLM.prompt +182 -0
  92. pdd/prompts/agentic_bug_step1_duplicate_LLM.prompt +73 -0
  93. pdd/prompts/agentic_bug_step2_docs_LLM.prompt +129 -0
  94. pdd/prompts/agentic_bug_step3_triage_LLM.prompt +95 -0
  95. pdd/prompts/agentic_bug_step4_reproduce_LLM.prompt +97 -0
  96. pdd/prompts/agentic_bug_step5_root_cause_LLM.prompt +123 -0
  97. pdd/prompts/agentic_bug_step6_test_plan_LLM.prompt +107 -0
  98. pdd/prompts/agentic_bug_step7_generate_LLM.prompt +172 -0
  99. pdd/prompts/agentic_bug_step8_verify_LLM.prompt +119 -0
  100. pdd/prompts/agentic_bug_step9_e2e_test_LLM.prompt +289 -0
  101. pdd/prompts/agentic_change_step10_identify_issues_LLM.prompt +1006 -0
  102. pdd/prompts/agentic_change_step11_fix_issues_LLM.prompt +984 -0
  103. pdd/prompts/agentic_change_step12_create_pr_LLM.prompt +131 -0
  104. pdd/prompts/agentic_change_step1_duplicate_LLM.prompt +73 -0
  105. pdd/prompts/agentic_change_step2_docs_LLM.prompt +101 -0
  106. pdd/prompts/agentic_change_step3_research_LLM.prompt +126 -0
  107. pdd/prompts/agentic_change_step4_clarify_LLM.prompt +164 -0
  108. pdd/prompts/agentic_change_step5_docs_change_LLM.prompt +981 -0
  109. pdd/prompts/agentic_change_step6_devunits_LLM.prompt +1005 -0
  110. pdd/prompts/agentic_change_step7_architecture_LLM.prompt +1044 -0
  111. pdd/prompts/agentic_change_step8_analyze_LLM.prompt +1027 -0
  112. pdd/prompts/agentic_change_step9_implement_LLM.prompt +1077 -0
  113. pdd/prompts/agentic_crash_explore_LLM.prompt +49 -0
  114. pdd/prompts/agentic_e2e_fix_step1_unit_tests_LLM.prompt +90 -0
  115. pdd/prompts/agentic_e2e_fix_step2_e2e_tests_LLM.prompt +91 -0
  116. pdd/prompts/agentic_e2e_fix_step3_root_cause_LLM.prompt +89 -0
  117. pdd/prompts/agentic_e2e_fix_step4_fix_e2e_tests_LLM.prompt +96 -0
  118. pdd/prompts/agentic_e2e_fix_step5_identify_devunits_LLM.prompt +91 -0
  119. pdd/prompts/agentic_e2e_fix_step6_create_unit_tests_LLM.prompt +106 -0
  120. pdd/prompts/agentic_e2e_fix_step7_verify_tests_LLM.prompt +116 -0
  121. pdd/prompts/agentic_e2e_fix_step8_run_pdd_fix_LLM.prompt +120 -0
  122. pdd/prompts/agentic_e2e_fix_step9_verify_all_LLM.prompt +146 -0
  123. pdd/prompts/agentic_fix_explore_LLM.prompt +45 -0
  124. pdd/prompts/agentic_fix_harvest_only_LLM.prompt +48 -0
  125. pdd/prompts/agentic_fix_primary_LLM.prompt +85 -0
  126. pdd/prompts/agentic_update_LLM.prompt +925 -0
  127. pdd/prompts/agentic_verify_explore_LLM.prompt +45 -0
  128. pdd/prompts/auto_include_LLM.prompt +122 -905
  129. pdd/prompts/change_LLM.prompt +3093 -1
  130. pdd/prompts/detect_change_LLM.prompt +686 -27
  131. pdd/prompts/example_generator_LLM.prompt +22 -1
  132. pdd/prompts/extract_code_LLM.prompt +5 -1
  133. pdd/prompts/extract_program_code_fix_LLM.prompt +7 -1
  134. pdd/prompts/extract_prompt_update_LLM.prompt +7 -8
  135. pdd/prompts/extract_promptline_LLM.prompt +17 -11
  136. pdd/prompts/find_verification_errors_LLM.prompt +6 -0
  137. pdd/prompts/fix_code_module_errors_LLM.prompt +12 -2
  138. pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +9 -0
  139. pdd/prompts/fix_verification_errors_LLM.prompt +22 -0
  140. pdd/prompts/generate_test_LLM.prompt +41 -7
  141. pdd/prompts/generate_test_from_example_LLM.prompt +115 -0
  142. pdd/prompts/increase_tests_LLM.prompt +1 -5
  143. pdd/prompts/insert_includes_LLM.prompt +316 -186
  144. pdd/prompts/prompt_code_diff_LLM.prompt +119 -0
  145. pdd/prompts/prompt_diff_LLM.prompt +82 -0
  146. pdd/prompts/trace_LLM.prompt +25 -22
  147. pdd/prompts/unfinished_prompt_LLM.prompt +85 -1
  148. pdd/prompts/update_prompt_LLM.prompt +22 -1
  149. pdd/pytest_output.py +127 -12
  150. pdd/remote_session.py +876 -0
  151. pdd/render_mermaid.py +236 -0
  152. pdd/server/__init__.py +52 -0
  153. pdd/server/app.py +335 -0
  154. pdd/server/click_executor.py +587 -0
  155. pdd/server/executor.py +338 -0
  156. pdd/server/jobs.py +661 -0
  157. pdd/server/models.py +241 -0
  158. pdd/server/routes/__init__.py +31 -0
  159. pdd/server/routes/architecture.py +451 -0
  160. pdd/server/routes/auth.py +364 -0
  161. pdd/server/routes/commands.py +929 -0
  162. pdd/server/routes/config.py +42 -0
  163. pdd/server/routes/files.py +603 -0
  164. pdd/server/routes/prompts.py +1322 -0
  165. pdd/server/routes/websocket.py +473 -0
  166. pdd/server/security.py +243 -0
  167. pdd/server/terminal_spawner.py +209 -0
  168. pdd/server/token_counter.py +222 -0
  169. pdd/setup_tool.py +648 -0
  170. pdd/simple_math.py +2 -0
  171. pdd/split_main.py +3 -2
  172. pdd/summarize_directory.py +237 -195
  173. pdd/sync_animation.py +8 -4
  174. pdd/sync_determine_operation.py +839 -112
  175. pdd/sync_main.py +351 -57
  176. pdd/sync_orchestration.py +1400 -756
  177. pdd/sync_tui.py +848 -0
  178. pdd/template_expander.py +161 -0
  179. pdd/template_registry.py +264 -0
  180. pdd/templates/architecture/architecture_json.prompt +237 -0
  181. pdd/templates/generic/generate_prompt.prompt +174 -0
  182. pdd/trace.py +168 -12
  183. pdd/trace_main.py +4 -3
  184. pdd/track_cost.py +140 -63
  185. pdd/unfinished_prompt.py +51 -4
  186. pdd/update_main.py +567 -67
  187. pdd/update_model_costs.py +2 -2
  188. pdd/update_prompt.py +19 -4
  189. {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/METADATA +29 -11
  190. pdd_cli-0.0.118.dist-info/RECORD +227 -0
  191. {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/licenses/LICENSE +1 -1
  192. pdd_cli-0.0.45.dist-info/RECORD +0 -116
  193. {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/WHEEL +0 -0
  194. {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/entry_points.txt +0 -0
  195. {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/top_level.txt +0 -0
@@ -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