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,290 @@
1
+ """
2
+ PDD Connect Command.
3
+
4
+ This module provides the `pdd connect` CLI command which launches a local
5
+ REST server to enable the web frontend to interact with PDD.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ import os
12
+ import webbrowser
13
+ from pathlib import Path
14
+ from typing import Optional
15
+
16
+ import click
17
+
18
+ # Handle optional dependencies - uvicorn may not be installed
19
+ try:
20
+ import uvicorn
21
+ except ImportError:
22
+ uvicorn = None
23
+
24
+ # Internal imports
25
+ # We wrap this in a try/except block to allow the module to be imported
26
+ # even if the server dependencies are not present (e.g. in partial environments)
27
+ try:
28
+ from ..server.app import create_app
29
+ except (ImportError, ValueError):
30
+ def create_app(*args, **kwargs):
31
+ raise ImportError("Could not import pdd.server.app.create_app. Ensure server dependencies are installed.")
32
+
33
+
34
+ @click.command("connect")
35
+ @click.option(
36
+ "--port",
37
+ default=9876,
38
+ help="Port to listen on",
39
+ show_default=True,
40
+ type=int,
41
+ )
42
+ @click.option(
43
+ "--host",
44
+ default="127.0.0.1",
45
+ help="Host to bind to",
46
+ show_default=True,
47
+ )
48
+ @click.option(
49
+ "--allow-remote",
50
+ is_flag=True,
51
+ help="Allow non-localhost connections",
52
+ )
53
+ @click.option(
54
+ "--token",
55
+ help="Bearer token for authentication",
56
+ default=None,
57
+ )
58
+ @click.option(
59
+ "--no-browser",
60
+ is_flag=True,
61
+ help="Don't open browser automatically",
62
+ )
63
+ @click.option(
64
+ "--frontend-url",
65
+ help="Custom frontend URL",
66
+ default=None,
67
+ )
68
+ @click.option(
69
+ "--local-only",
70
+ is_flag=True,
71
+ help="Skip cloud registration (local access only)",
72
+ )
73
+ @click.option(
74
+ "--session-name",
75
+ help="Custom session name for identification",
76
+ default=None,
77
+ )
78
+ @click.pass_context
79
+ def connect(
80
+ ctx: click.Context,
81
+ port: int,
82
+ host: str,
83
+ allow_remote: bool,
84
+ token: Optional[str],
85
+ no_browser: bool,
86
+ frontend_url: Optional[str],
87
+ local_only: bool,
88
+ session_name: Optional[str],
89
+ ) -> None:
90
+ """
91
+ Launch the local REST server for the PDD web frontend.
92
+
93
+ This command starts a FastAPI server that exposes the PDD functionality
94
+ via a REST API. It automatically opens the web interface in your default
95
+ browser unless --no-browser is specified.
96
+
97
+ For authenticated users, the session is automatically registered with
98
+ PDD Cloud for remote access. Use --local-only to skip cloud registration.
99
+ """
100
+ # Check uvicorn is available
101
+ if uvicorn is None:
102
+ click.echo(click.style("Error: 'uvicorn' is not installed. Please install it to use the connect command.", fg="red"))
103
+ ctx.exit(1)
104
+
105
+ # 1. Determine Project Root
106
+ # We assume the current working directory is the project root
107
+ project_root = Path.cwd()
108
+
109
+ # 2. Security Checks & Configuration
110
+ if allow_remote:
111
+ if not token:
112
+ click.echo(click.style(
113
+ "SECURITY WARNING: You are allowing remote connections without an authentication token.",
114
+ fg="red", bold=True
115
+ ))
116
+ click.echo("Anyone with access to your network could execute code on your machine.")
117
+ if not click.confirm("Do you want to proceed?"):
118
+ ctx.exit(1)
119
+
120
+ # If user explicitly asked for remote but left host as localhost,
121
+ # bind to all interfaces to actually allow remote connections.
122
+ if host == "127.0.0.1":
123
+ host = "0.0.0.0"
124
+ click.echo(click.style("Binding to 0.0.0.0 to allow remote connections.", fg="yellow"))
125
+ else:
126
+ # Warn if binding to non-localhost without explicit allow-remote
127
+ if host not in ("127.0.0.1", "localhost"):
128
+ click.echo(click.style(
129
+ f"Warning: Binding to {host} without --allow-remote flag. "
130
+ "External connections may be blocked or insecure.",
131
+ fg="yellow"
132
+ ))
133
+
134
+ # 3. Determine URLs
135
+ # The server URL is where the API lives
136
+ server_url = f"http://{host}:{port}"
137
+
138
+ # The frontend URL is what we open in the browser
139
+ # If binding to 0.0.0.0, we still use localhost for the local browser
140
+ browser_host = "localhost" if host == "0.0.0.0" else host
141
+ target_url = frontend_url if frontend_url else f"http://{browser_host}:{port}"
142
+
143
+ # 4. Configure CORS
144
+ # We need to allow the frontend to talk to the backend
145
+ allowed_origins = [
146
+ "http://localhost:3000",
147
+ "http://127.0.0.1:3000",
148
+ "http://localhost:5173",
149
+ "http://127.0.0.1:5173",
150
+ f"http://localhost:{port}",
151
+ f"http://127.0.0.1:{port}",
152
+ # PDD Cloud frontend
153
+ "https://pdd.dev",
154
+ "https://www.pdd.dev",
155
+ ]
156
+ if frontend_url:
157
+ allowed_origins.append(frontend_url)
158
+
159
+ # 4.5 Cloud Session Registration (automatic for authenticated users)
160
+ session_manager = None
161
+ cloud_url = None
162
+ if not local_only:
163
+ try:
164
+ from ..core.cloud import CloudConfig
165
+ from ..remote_session import (
166
+ RemoteSessionManager,
167
+ RemoteSessionError,
168
+ set_active_session_manager,
169
+ )
170
+
171
+ # Check if user is authenticated
172
+ jwt_token = CloudConfig.get_jwt_token(verbose=False)
173
+ if not jwt_token:
174
+ click.echo(click.style(
175
+ "Not authenticated. Running in local-only mode.",
176
+ dim=True
177
+ ))
178
+ click.echo(click.style(
179
+ "Run 'pdd login' to enable remote access via cloud.",
180
+ dim=True
181
+ ))
182
+ else:
183
+ click.echo("Registering session with PDD Cloud...")
184
+ session_manager = RemoteSessionManager(jwt_token, project_root)
185
+ try:
186
+ # Register with cloud - no public URL needed, cloud hosts everything
187
+ cloud_url = asyncio.run(session_manager.register(
188
+ session_name=session_name,
189
+ ))
190
+ # Heartbeat will be started by the app's lifespan manager
191
+ set_active_session_manager(session_manager)
192
+
193
+ click.echo(click.style(
194
+ "Session registered with PDD Cloud!", fg="green", bold=True
195
+ ))
196
+ # TODO: Re-enable when production /connect page is deployed
197
+ # click.echo(f" Access URL: {click.style(cloud_url, fg='cyan', underline=True)}")
198
+ # click.echo(click.style(
199
+ # " Share this URL to access your PDD session from any browser.",
200
+ # dim=True
201
+ # ))
202
+ except RemoteSessionError as e:
203
+ click.echo(click.style(
204
+ f"Warning: Failed to register with cloud: {e.message}",
205
+ fg="yellow"
206
+ ))
207
+ click.echo(click.style(
208
+ "Running in local-only mode.",
209
+ dim=True
210
+ ))
211
+ session_manager = None
212
+ except ImportError as e:
213
+ click.echo(click.style(
214
+ f"Running in local-only mode (cloud dependencies not available).",
215
+ dim=True
216
+ ))
217
+ else:
218
+ click.echo(click.style(
219
+ "Running in local-only mode (--local-only flag set).",
220
+ dim=True
221
+ ))
222
+
223
+ # 5. Initialize Server App
224
+ try:
225
+ # Pass token via environment variable if provided, as create_app might not take it directly
226
+ if token:
227
+ os.environ["PDD_ACCESS_TOKEN"] = token
228
+
229
+ app = create_app(project_root, allowed_origins=allowed_origins)
230
+ except Exception as e:
231
+ click.echo(click.style(f"Failed to initialize server: {e}", fg="red", bold=True))
232
+ ctx.exit(1)
233
+
234
+ # 6. Print Status Messages
235
+ click.echo(click.style(f"Starting PDD server on {server_url}", fg="green", bold=True))
236
+ click.echo(f"Project Root: {click.style(str(project_root), fg='blue')}")
237
+ click.echo(f"API Documentation: {click.style(f'{server_url}/docs', underline=True)}")
238
+ click.echo(f"Local Frontend: {click.style(target_url, underline=True)}")
239
+ # TODO: Re-enable when production /connect page is deployed
240
+ # if cloud_url:
241
+ # click.echo(f"Remote Access: {click.style(cloud_url, fg='cyan', underline=True)}")
242
+ click.echo(click.style("Press Ctrl+C to stop the server", dim=True))
243
+
244
+ # 7. Open Browser
245
+ if not no_browser:
246
+ # Import remote session detection
247
+ from ..core.remote_session import is_remote_session
248
+
249
+ is_remote, reason = is_remote_session()
250
+ if is_remote:
251
+ click.echo(click.style(f"Note: {reason}", fg="yellow"))
252
+ click.echo("Opening browser may not work in remote sessions. Use the URL above to connect manually.")
253
+
254
+ click.echo("Opening browser...")
255
+ try:
256
+ webbrowser.open(target_url)
257
+ except Exception as e:
258
+ click.echo(click.style(f"Could not open browser: {e}", fg="yellow"))
259
+ click.echo(f"Please open {target_url} manually in your browser.")
260
+
261
+ # 8. Run Server
262
+ try:
263
+ # Run uvicorn
264
+ # Disable access_log to avoid noisy polling logs - custom middleware handles important logging
265
+ uvicorn.run(
266
+ app,
267
+ host=host,
268
+ port=port,
269
+ log_level="warning", # Only show warnings and errors from uvicorn
270
+ access_log=False # Custom middleware handles request logging
271
+ )
272
+ except KeyboardInterrupt:
273
+ click.echo(click.style("\nServer stopping...", fg="yellow", bold=True))
274
+ except Exception as e:
275
+ click.echo(click.style(f"\nServer error: {e}", fg="red", bold=True))
276
+ ctx.exit(1)
277
+ finally:
278
+ # Clean up cloud session if registered
279
+ if session_manager is not None:
280
+ click.echo("Deregistering from PDD Cloud...")
281
+ try:
282
+ from ..remote_session import set_active_session_manager
283
+ asyncio.run(session_manager.stop_heartbeat())
284
+ asyncio.run(session_manager.deregister())
285
+ set_active_session_manager(None)
286
+ click.echo(click.style("Session deregistered.", fg="green"))
287
+ except Exception as e:
288
+ click.echo(click.style(f"Warning: Error during session cleanup: {e}", fg="yellow"))
289
+
290
+ click.echo(click.style("Goodbye!", fg="blue"))
pdd/commands/fix.py ADDED
@@ -0,0 +1,163 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ import click
5
+ from typing import Optional, Tuple, Any
6
+ from rich.console import Console
7
+
8
+ # Relative imports for internal modules
9
+ from ..fix_main import fix_main
10
+ from ..agentic_e2e_fix import run_agentic_e2e_fix
11
+ from ..track_cost import track_cost
12
+ from ..core.errors import handle_error
13
+
14
+ console = Console()
15
+
16
+ @click.command(name="fix")
17
+ @click.argument("args", nargs=-1)
18
+ @click.option("--manual", is_flag=True, help="Use manual mode with explicit file arguments.")
19
+ @click.option("--timeout-adder", type=float, default=0.0, help="Additional seconds to add to each step's timeout (Agentic mode).")
20
+ @click.option("--max-cycles", type=int, default=5, help="Maximum number of outer loop cycles (Agentic mode).")
21
+ @click.option("--resume/--no-resume", default=True, help="Resume from saved state if available (Agentic mode).")
22
+ @click.option("--force", is_flag=True, help="Override branch mismatch safety check (Agentic mode).")
23
+ @click.option("--no-github-state", is_flag=True, help="Disable GitHub issue comment-based state persistence (Agentic mode).")
24
+ @click.option("--output-test", type=click.Path(), help="Specify where to save the fixed unit test file.")
25
+ @click.option("--output-code", type=click.Path(), help="Specify where to save the fixed code file.")
26
+ @click.option("--output-results", type=click.Path(), help="Specify where to save the results log.")
27
+ @click.option("--loop", is_flag=True, help="Enable iterative fixing process.")
28
+ @click.option("--verification-program", type=click.Path(), help="Path to verification program (required for --loop).")
29
+ @click.option("--max-attempts", type=int, default=3, help="Maximum number of fix attempts.")
30
+ @click.option("--budget", type=float, default=5.0, help="Maximum cost allowed for the fixing process.")
31
+ @click.option("--auto-submit", is_flag=True, help="Automatically submit example if tests pass.")
32
+ @click.option("--agentic-fallback/--no-agentic-fallback", default=True, help="Enable agentic fallback in loop mode.")
33
+ @click.pass_context
34
+ @track_cost
35
+ def fix(
36
+ ctx: click.Context,
37
+ args: Tuple[str, ...],
38
+ manual: bool,
39
+ timeout_adder: float,
40
+ max_cycles: int,
41
+ resume: bool,
42
+ force: bool,
43
+ no_github_state: bool,
44
+ output_test: Optional[str],
45
+ output_code: Optional[str],
46
+ output_results: Optional[str],
47
+ loop: bool,
48
+ verification_program: Optional[str],
49
+ max_attempts: int,
50
+ budget: float,
51
+ auto_submit: bool,
52
+ agentic_fallback: bool,
53
+ ) -> Optional[Tuple[Any, float, str]]:
54
+ """
55
+ Fix errors in code and unit tests.
56
+
57
+ Supports two modes:
58
+ 1. Agentic E2E Fix: pdd fix <GITHUB_ISSUE_URL>
59
+ 2. Manual Mode: pdd fix --manual PROMPT_FILE CODE_FILE UNIT_TEST_FILE... ERROR_FILE
60
+ """
61
+ try:
62
+ if not args:
63
+ raise click.UsageError("Missing arguments. See 'pdd fix --help'.")
64
+
65
+ # Determine mode based on first argument
66
+ # If it looks like a URL and --manual is not set, use Agentic mode
67
+ is_url = args[0].startswith("http") or "github.com" in args[0]
68
+
69
+ # --- Agentic E2E Fix Mode ---
70
+ if is_url and not manual:
71
+ if len(args) > 1:
72
+ console.print("[yellow]Warning: Extra arguments ignored in Agentic E2E Fix mode.[/yellow]")
73
+
74
+ issue_url = args[0]
75
+ verbose = ctx.obj.get("verbose", False)
76
+ quiet = ctx.obj.get("quiet", False)
77
+
78
+ # Call the agentic fix workflow
79
+ success, message, cost, model, _ = run_agentic_e2e_fix(
80
+ issue_url=issue_url,
81
+ timeout_adder=timeout_adder,
82
+ max_cycles=max_cycles,
83
+ resume=resume,
84
+ force=force,
85
+ verbose=verbose,
86
+ quiet=quiet,
87
+ use_github_state=not no_github_state
88
+ )
89
+
90
+ if not success:
91
+ console.print(f"[bold red]Agentic fix failed:[/bold red] {message}")
92
+ else:
93
+ console.print(f"[bold green]Agentic fix completed:[/bold green] {message}")
94
+
95
+ return message, cost, model
96
+
97
+ # --- Manual Mode ---
98
+ else:
99
+ # Validate arguments for manual mode
100
+ # Expected structure: PROMPT_FILE CODE_FILE UNIT_TEST_FILE [UNIT_TEST_FILE...] ERROR_FILE
101
+ if len(args) < 4:
102
+ raise click.UsageError(
103
+ "Manual mode requires at least 4 arguments: PROMPT_FILE CODE_FILE UNIT_TEST_FILE... ERROR_FILE"
104
+ )
105
+
106
+ prompt_file = args[0]
107
+ code_file = args[1]
108
+ error_file = args[-1]
109
+ # All arguments between code file and error file are treated as unit test files
110
+ unit_test_files = args[2:-1]
111
+
112
+ total_cost = 0.0
113
+ last_model = "unknown"
114
+ all_success = True
115
+ results_summary = []
116
+
117
+ # Process each unit test file
118
+ for i, test_file in enumerate(unit_test_files):
119
+ if len(unit_test_files) > 1:
120
+ console.print(f"[bold blue]Processing test file {i+1}/{len(unit_test_files)}: {test_file}[/bold blue]")
121
+
122
+ # Call the core fix logic
123
+ # Note: If multiple test files are processed, output_test will overwrite
124
+ # the same location if specified, as per documentation warning.
125
+ success, _, _, _, cost, model = fix_main(
126
+ ctx=ctx,
127
+ prompt_file=prompt_file,
128
+ code_file=code_file,
129
+ unit_test_file=test_file,
130
+ error_file=error_file,
131
+ output_test=output_test,
132
+ output_code=output_code,
133
+ output_results=output_results,
134
+ loop=loop,
135
+ verification_program=verification_program,
136
+ max_attempts=max_attempts,
137
+ budget=budget,
138
+ auto_submit=auto_submit,
139
+ agentic_fallback=agentic_fallback,
140
+ strength=None, # Use context defaults inside fix_main
141
+ temperature=None # Use context defaults inside fix_main
142
+ )
143
+
144
+ total_cost += cost
145
+ last_model = model
146
+ if not success:
147
+ all_success = False
148
+
149
+ status = "Fixed" if success else "Failed"
150
+ results_summary.append(f"{test_file}: {status}")
151
+
152
+ # Construct return message
153
+ summary_str = "\n".join(results_summary)
154
+ if all_success:
155
+ return f"All files processed successfully.\n{summary_str}", total_cost, last_model
156
+ else:
157
+ return f"Some files failed to fix.\n{summary_str}", total_cost, last_model
158
+
159
+ except click.Abort:
160
+ raise
161
+ except Exception as e:
162
+ handle_error(e)
163
+ return None