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/connect.py
ADDED
|
@@ -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
|