pdd-cli 0.0.90__py3-none-any.whl → 0.0.121__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 +38 -6
- pdd/agentic_bug.py +323 -0
- pdd/agentic_bug_orchestrator.py +506 -0
- pdd/agentic_change.py +231 -0
- pdd/agentic_change_orchestrator.py +537 -0
- pdd/agentic_common.py +533 -770
- pdd/agentic_crash.py +2 -1
- pdd/agentic_e2e_fix.py +319 -0
- pdd/agentic_e2e_fix_orchestrator.py +582 -0
- pdd/agentic_fix.py +118 -3
- pdd/agentic_update.py +27 -9
- pdd/agentic_verify.py +3 -2
- pdd/architecture_sync.py +565 -0
- pdd/auth_service.py +210 -0
- pdd/auto_deps_main.py +63 -53
- pdd/auto_include.py +236 -3
- pdd/auto_update.py +125 -47
- pdd/bug_main.py +195 -23
- pdd/cmd_test_main.py +345 -197
- pdd/code_generator.py +4 -2
- pdd/code_generator_main.py +118 -32
- pdd/commands/__init__.py +6 -0
- pdd/commands/analysis.py +113 -48
- pdd/commands/auth.py +309 -0
- pdd/commands/connect.py +358 -0
- pdd/commands/fix.py +155 -114
- pdd/commands/generate.py +5 -0
- pdd/commands/maintenance.py +3 -2
- pdd/commands/misc.py +8 -0
- pdd/commands/modify.py +225 -163
- pdd/commands/sessions.py +284 -0
- pdd/commands/utility.py +12 -7
- pdd/construct_paths.py +334 -32
- pdd/context_generator_main.py +167 -170
- pdd/continue_generation.py +6 -3
- pdd/core/__init__.py +33 -0
- pdd/core/cli.py +44 -7
- pdd/core/cloud.py +237 -0
- pdd/core/dump.py +68 -20
- pdd/core/errors.py +4 -0
- pdd/core/remote_session.py +61 -0
- pdd/crash_main.py +219 -23
- pdd/data/llm_model.csv +4 -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 +208 -34
- pdd/fix_code_module_errors.py +6 -2
- pdd/fix_error_loop.py +291 -38
- pdd/fix_main.py +208 -6
- pdd/fix_verification_errors_loop.py +235 -26
- pdd/fix_verification_main.py +269 -83
- pdd/frontend/dist/assets/index-B5DZHykP.css +1 -0
- pdd/frontend/dist/assets/index-CUWd8al1.js +450 -0
- pdd/frontend/dist/index.html +376 -0
- pdd/frontend/dist/logo.svg +33 -0
- pdd/generate_output_paths.py +46 -5
- pdd/generate_test.py +212 -151
- pdd/get_comment.py +19 -44
- pdd/get_extension.py +8 -9
- pdd/get_jwt_token.py +309 -20
- pdd/get_language.py +8 -7
- pdd/get_run_command.py +7 -5
- pdd/insert_includes.py +2 -1
- pdd/llm_invoke.py +531 -97
- pdd/load_prompt_template.py +15 -34
- pdd/operation_log.py +342 -0
- pdd/path_resolution.py +140 -0
- pdd/postprocess.py +122 -97
- pdd/preprocess.py +68 -12
- pdd/preprocess_main.py +33 -1
- 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 +140 -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_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_primary_LLM.prompt +2 -2
- pdd/prompts/agentic_update_LLM.prompt +192 -338
- pdd/prompts/auto_include_LLM.prompt +22 -0
- pdd/prompts/change_LLM.prompt +3093 -1
- pdd/prompts/detect_change_LLM.prompt +571 -14
- pdd/prompts/fix_code_module_errors_LLM.prompt +8 -0
- pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +1 -0
- pdd/prompts/generate_test_LLM.prompt +19 -1
- pdd/prompts/generate_test_from_example_LLM.prompt +366 -0
- pdd/prompts/insert_includes_LLM.prompt +262 -252
- pdd/prompts/prompt_code_diff_LLM.prompt +123 -0
- pdd/prompts/prompt_diff_LLM.prompt +82 -0
- pdd/remote_session.py +876 -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 +1347 -0
- pdd/server/routes/websocket.py +473 -0
- pdd/server/security.py +243 -0
- pdd/server/terminal_spawner.py +217 -0
- pdd/server/token_counter.py +222 -0
- pdd/summarize_directory.py +236 -237
- pdd/sync_animation.py +8 -4
- pdd/sync_determine_operation.py +329 -47
- pdd/sync_main.py +272 -28
- pdd/sync_orchestration.py +289 -211
- pdd/sync_order.py +304 -0
- pdd/template_expander.py +161 -0
- pdd/templates/architecture/architecture_json.prompt +41 -46
- pdd/trace.py +1 -1
- pdd/track_cost.py +0 -13
- pdd/unfinished_prompt.py +2 -1
- pdd/update_main.py +68 -26
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/METADATA +15 -10
- pdd_cli-0.0.121.dist-info/RECORD +229 -0
- pdd_cli-0.0.90.dist-info/RECORD +0 -153
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/licenses/LICENSE +0 -0
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/top_level.txt +0 -0
pdd/fix_error_loop.py
CHANGED
|
@@ -6,9 +6,12 @@ import shutil
|
|
|
6
6
|
import json
|
|
7
7
|
from datetime import datetime
|
|
8
8
|
from pathlib import Path
|
|
9
|
+
from typing import Tuple, Optional
|
|
9
10
|
|
|
11
|
+
import requests
|
|
10
12
|
from rich import print as rprint
|
|
11
13
|
from rich.console import Console
|
|
14
|
+
from rich.panel import Panel
|
|
12
15
|
|
|
13
16
|
# Relative import from an internal module.
|
|
14
17
|
from .get_language import get_language
|
|
@@ -17,7 +20,10 @@ from . import DEFAULT_TIME # Import DEFAULT_TIME
|
|
|
17
20
|
from .python_env_detector import detect_host_python_executable
|
|
18
21
|
from .agentic_fix import run_agentic_fix
|
|
19
22
|
from .agentic_langtest import default_verify_cmd_for
|
|
23
|
+
from .core.cloud import CloudConfig
|
|
20
24
|
|
|
25
|
+
# Cloud request timeout for LLM calls
|
|
26
|
+
CLOUD_FIX_TIMEOUT = 400 # seconds
|
|
21
27
|
|
|
22
28
|
console = Console()
|
|
23
29
|
|
|
@@ -25,6 +31,133 @@ def escape_brackets(text: str) -> str:
|
|
|
25
31
|
"""Escape square brackets so Rich doesn't misinterpret them."""
|
|
26
32
|
return text.replace("[", "\\[").replace("]", "\\]")
|
|
27
33
|
|
|
34
|
+
|
|
35
|
+
def cloud_fix_errors(
|
|
36
|
+
unit_test: str,
|
|
37
|
+
code: str,
|
|
38
|
+
prompt: str,
|
|
39
|
+
error: str,
|
|
40
|
+
error_file: str,
|
|
41
|
+
strength: float,
|
|
42
|
+
temperature: float,
|
|
43
|
+
verbose: bool = False,
|
|
44
|
+
time: float = DEFAULT_TIME,
|
|
45
|
+
code_file_ext: str = ".py"
|
|
46
|
+
) -> Tuple[bool, bool, str, str, str, float, str]:
|
|
47
|
+
"""
|
|
48
|
+
Call the cloud fixCode endpoint to fix errors in code and unit tests.
|
|
49
|
+
|
|
50
|
+
This function has the same interface as fix_errors_from_unit_tests to allow
|
|
51
|
+
seamless switching between local and cloud execution in the fix loop.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
unit_test: Unit test code string
|
|
55
|
+
code: Source code string
|
|
56
|
+
prompt: Prompt that generated the code
|
|
57
|
+
error: Error messages/logs from test failures
|
|
58
|
+
error_file: Path to write error analysis (not used in cloud, but kept for interface compatibility)
|
|
59
|
+
strength: Model strength parameter [0,1]
|
|
60
|
+
temperature: Model temperature parameter [0,1]
|
|
61
|
+
verbose: Enable verbose logging
|
|
62
|
+
time: Time budget for thinking effort
|
|
63
|
+
code_file_ext: File extension to determine language (e.g., ".py", ".java")
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Tuple of:
|
|
67
|
+
- update_unit_test: Whether unit test was updated
|
|
68
|
+
- update_code: Whether code was updated
|
|
69
|
+
- fixed_unit_test: Fixed unit test code
|
|
70
|
+
- fixed_code: Fixed source code
|
|
71
|
+
- analysis: Analysis/explanation of fixes
|
|
72
|
+
- total_cost: Cost of the operation
|
|
73
|
+
- model_name: Name of model used
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
RuntimeError: When cloud execution fails with non-recoverable error
|
|
77
|
+
"""
|
|
78
|
+
jwt_token = CloudConfig.get_jwt_token(verbose=verbose)
|
|
79
|
+
|
|
80
|
+
if not jwt_token:
|
|
81
|
+
raise RuntimeError("Cloud authentication failed - no JWT token available")
|
|
82
|
+
|
|
83
|
+
# Build cloud payload
|
|
84
|
+
payload = {
|
|
85
|
+
"unitTest": unit_test,
|
|
86
|
+
"code": code,
|
|
87
|
+
"prompt": prompt,
|
|
88
|
+
"errors": error,
|
|
89
|
+
"language": get_language(code_file_ext),
|
|
90
|
+
"strength": strength,
|
|
91
|
+
"temperature": temperature,
|
|
92
|
+
"time": time if time is not None else 0.25,
|
|
93
|
+
"verbose": verbose,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
headers = {
|
|
97
|
+
"Authorization": f"Bearer {jwt_token}",
|
|
98
|
+
"Content-Type": "application/json"
|
|
99
|
+
}
|
|
100
|
+
cloud_url = CloudConfig.get_endpoint_url("fixCode")
|
|
101
|
+
|
|
102
|
+
if verbose:
|
|
103
|
+
console.print(Panel(f"Calling cloud fix at {cloud_url}", title="[blue]Cloud LLM[/blue]", expand=False))
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
response = requests.post(
|
|
107
|
+
cloud_url,
|
|
108
|
+
json=payload,
|
|
109
|
+
headers=headers,
|
|
110
|
+
timeout=CLOUD_FIX_TIMEOUT
|
|
111
|
+
)
|
|
112
|
+
response.raise_for_status()
|
|
113
|
+
|
|
114
|
+
response_data = response.json()
|
|
115
|
+
fixed_unit_test = response_data.get("fixedUnitTest", "")
|
|
116
|
+
fixed_code = response_data.get("fixedCode", "")
|
|
117
|
+
analysis = response_data.get("analysis", "")
|
|
118
|
+
total_cost = float(response_data.get("totalCost", 0.0))
|
|
119
|
+
model_name = response_data.get("modelName", "cloud_model")
|
|
120
|
+
update_unit_test = response_data.get("updateUnitTest", False)
|
|
121
|
+
update_code = response_data.get("updateCode", False)
|
|
122
|
+
|
|
123
|
+
if verbose:
|
|
124
|
+
console.print(f"[cyan]Cloud fix completed. Model: {model_name}, Cost: ${total_cost:.6f}[/cyan]")
|
|
125
|
+
|
|
126
|
+
return update_unit_test, update_code, fixed_unit_test, fixed_code, analysis, total_cost, model_name
|
|
127
|
+
|
|
128
|
+
except requests.exceptions.Timeout:
|
|
129
|
+
raise RuntimeError(f"Cloud fix timed out after {CLOUD_FIX_TIMEOUT}s")
|
|
130
|
+
|
|
131
|
+
except requests.exceptions.HTTPError as e:
|
|
132
|
+
status_code = e.response.status_code if e.response else 0
|
|
133
|
+
err_content = e.response.text[:200] if e.response else "No response content"
|
|
134
|
+
|
|
135
|
+
# Non-recoverable errors
|
|
136
|
+
if status_code == 402:
|
|
137
|
+
try:
|
|
138
|
+
error_data = e.response.json()
|
|
139
|
+
current_balance = error_data.get("currentBalance", "unknown")
|
|
140
|
+
estimated_cost = error_data.get("estimatedCost", "unknown")
|
|
141
|
+
raise RuntimeError(f"Insufficient credits. Balance: {current_balance}, estimated cost: {estimated_cost}")
|
|
142
|
+
except json.JSONDecodeError:
|
|
143
|
+
raise RuntimeError(f"Insufficient credits: {err_content}")
|
|
144
|
+
elif status_code == 401:
|
|
145
|
+
raise RuntimeError(f"Authentication failed: {err_content}")
|
|
146
|
+
elif status_code == 403:
|
|
147
|
+
raise RuntimeError(f"Access denied: {err_content}")
|
|
148
|
+
elif status_code == 400:
|
|
149
|
+
raise RuntimeError(f"Invalid request: {err_content}")
|
|
150
|
+
else:
|
|
151
|
+
# 5xx or other errors - raise for caller to handle
|
|
152
|
+
raise RuntimeError(f"Cloud HTTP error ({status_code}): {err_content}")
|
|
153
|
+
|
|
154
|
+
except requests.exceptions.RequestException as e:
|
|
155
|
+
raise RuntimeError(f"Cloud network error: {e}")
|
|
156
|
+
|
|
157
|
+
except json.JSONDecodeError:
|
|
158
|
+
raise RuntimeError("Cloud returned invalid JSON response")
|
|
159
|
+
|
|
160
|
+
|
|
28
161
|
# ---------- Normalize any agentic return shape to a 4-tuple ----------
|
|
29
162
|
def _normalize_agentic_result(result):
|
|
30
163
|
"""
|
|
@@ -137,15 +270,21 @@ def fix_error_loop(unit_test_file: str,
|
|
|
137
270
|
error_log_file: str = "error_log.txt",
|
|
138
271
|
verbose: bool = False,
|
|
139
272
|
time: float = DEFAULT_TIME,
|
|
140
|
-
agentic_fallback: bool = True
|
|
273
|
+
agentic_fallback: bool = True,
|
|
274
|
+
use_cloud: bool = False):
|
|
141
275
|
"""
|
|
142
|
-
Attempt to fix errors in a unit test and corresponding code using repeated iterations,
|
|
143
|
-
counting only the number of times we actually call the LLM fix function.
|
|
276
|
+
Attempt to fix errors in a unit test and corresponding code using repeated iterations,
|
|
277
|
+
counting only the number of times we actually call the LLM fix function.
|
|
144
278
|
The tests are re-run in the same iteration after a fix to see if we've succeeded,
|
|
145
279
|
so that 'attempts' matches the number of fix attempts (not the total test runs).
|
|
146
280
|
|
|
147
281
|
This updated version uses structured logging to avoid redundant entries.
|
|
148
282
|
|
|
283
|
+
Hybrid Cloud Support:
|
|
284
|
+
When use_cloud=True, the LLM fix calls are routed to the cloud fixCode endpoint
|
|
285
|
+
while local test execution (pytest, verification programs) stays local. This allows
|
|
286
|
+
the loop to pass local test results to the cloud for analysis and fixes.
|
|
287
|
+
|
|
149
288
|
Inputs:
|
|
150
289
|
unit_test_file: Path to the file containing unit tests.
|
|
151
290
|
code_file: Path to the file containing the code under test.
|
|
@@ -159,6 +298,7 @@ def fix_error_loop(unit_test_file: str,
|
|
|
159
298
|
verbose: Enable verbose logging (default: False).
|
|
160
299
|
time: Time parameter for the fix_errors_from_unit_tests call.
|
|
161
300
|
agentic_fallback: Whether to trigger cli agentic fallback when fix fails.
|
|
301
|
+
use_cloud: If True, use cloud LLM for fix calls while keeping test execution local.
|
|
162
302
|
Outputs:
|
|
163
303
|
success: Boolean indicating if the overall process succeeded.
|
|
164
304
|
final_unit_test: String contents of the final unit test file.
|
|
@@ -213,9 +353,10 @@ def fix_error_loop(unit_test_file: str,
|
|
|
213
353
|
|
|
214
354
|
# We do up to max_attempts fix attempts or until budget is exceeded
|
|
215
355
|
iteration = 0
|
|
356
|
+
# Determine if target is Python (moved before try block for use in exception handler)
|
|
357
|
+
is_python = str(code_file).lower().endswith(".py")
|
|
216
358
|
# Run an initial test to determine starting state
|
|
217
359
|
try:
|
|
218
|
-
is_python = str(code_file).lower().endswith(".py")
|
|
219
360
|
if is_python:
|
|
220
361
|
initial_fails, initial_errors, initial_warnings, pytest_output = run_pytest_on_file(unit_test_file)
|
|
221
362
|
else:
|
|
@@ -224,8 +365,44 @@ def fix_error_loop(unit_test_file: str,
|
|
|
224
365
|
lang = get_language(os.path.splitext(code_file)[1])
|
|
225
366
|
verify_cmd = default_verify_cmd_for(lang, unit_test_file)
|
|
226
367
|
if not verify_cmd:
|
|
227
|
-
|
|
228
|
-
|
|
368
|
+
# No verify command available (e.g., Java without maven/gradle).
|
|
369
|
+
# Trigger agentic fallback directly.
|
|
370
|
+
rprint(f"[cyan]No verification command for {lang}. Triggering agentic fallback directly...[/cyan]")
|
|
371
|
+
error_log_path = Path(error_log_file)
|
|
372
|
+
error_log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
373
|
+
if not error_log_path.exists() or error_log_path.stat().st_size == 0:
|
|
374
|
+
with open(error_log_path, "w") as f:
|
|
375
|
+
f.write(f"No verification command available for language: {lang}\n")
|
|
376
|
+
f.write("Agentic fix will attempt to resolve the issue.\n")
|
|
377
|
+
|
|
378
|
+
rprint(f"[cyan]Attempting agentic fix fallback (prompt_file={prompt_file!r})...[/cyan]")
|
|
379
|
+
success, agent_msg, agent_cost, agent_model, agent_changed_files = _safe_run_agentic_fix(
|
|
380
|
+
prompt_file=prompt_file,
|
|
381
|
+
code_file=code_file,
|
|
382
|
+
unit_test_file=unit_test_file,
|
|
383
|
+
error_log_file=error_log_file,
|
|
384
|
+
cwd=None, # Use project root (cwd), not prompt file's parent
|
|
385
|
+
)
|
|
386
|
+
if not success:
|
|
387
|
+
rprint(f"[bold red]Agentic fix fallback failed: {agent_msg}[/bold red]")
|
|
388
|
+
if agent_changed_files:
|
|
389
|
+
rprint(f"[cyan]Agent modified {len(agent_changed_files)} file(s):[/cyan]")
|
|
390
|
+
for f in agent_changed_files:
|
|
391
|
+
rprint(f" • {f}")
|
|
392
|
+
final_unit_test = ""
|
|
393
|
+
final_code = ""
|
|
394
|
+
try:
|
|
395
|
+
with open(unit_test_file, "r") as f:
|
|
396
|
+
final_unit_test = f.read()
|
|
397
|
+
except Exception:
|
|
398
|
+
pass
|
|
399
|
+
try:
|
|
400
|
+
with open(code_file, "r") as f:
|
|
401
|
+
final_code = f.read()
|
|
402
|
+
except Exception:
|
|
403
|
+
pass
|
|
404
|
+
return success, final_unit_test, final_code, 1, agent_cost, agent_model
|
|
405
|
+
|
|
229
406
|
verify_result = subprocess.run(verify_cmd, capture_output=True, text=True, shell=True, stdin=subprocess.DEVNULL)
|
|
230
407
|
pytest_output = (verify_result.stdout or "") + "\n" + (verify_result.stderr or "")
|
|
231
408
|
if verify_result.returncode == 0:
|
|
@@ -246,7 +423,43 @@ def fix_error_loop(unit_test_file: str,
|
|
|
246
423
|
}
|
|
247
424
|
except Exception as e:
|
|
248
425
|
rprint(f"[red]Error running initial test/verification:[/red] {e}")
|
|
249
|
-
|
|
426
|
+
# Instead of returning early, trigger agentic fallback if enabled (Issue #266)
|
|
427
|
+
if agentic_fallback:
|
|
428
|
+
rprint("[cyan]Initial test failed with exception. Triggering agentic fallback...[/cyan]")
|
|
429
|
+
error_log_path = Path(error_log_file)
|
|
430
|
+
error_log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
431
|
+
with open(error_log_path, "w") as f:
|
|
432
|
+
f.write(f"Initial test/verification failed with exception:\n{e}\n")
|
|
433
|
+
|
|
434
|
+
success, agent_msg, agent_cost, agent_model, agent_changed_files = _safe_run_agentic_fix(
|
|
435
|
+
prompt_file=prompt_file,
|
|
436
|
+
code_file=code_file,
|
|
437
|
+
unit_test_file=unit_test_file,
|
|
438
|
+
error_log_file=error_log_file,
|
|
439
|
+
cwd=None,
|
|
440
|
+
)
|
|
441
|
+
if not success:
|
|
442
|
+
rprint(f"[bold red]Agentic fix fallback failed: {agent_msg}[/bold red]")
|
|
443
|
+
if agent_changed_files:
|
|
444
|
+
rprint(f"[cyan]Agent modified {len(agent_changed_files)} file(s):[/cyan]")
|
|
445
|
+
for f in agent_changed_files:
|
|
446
|
+
rprint(f" • {f}")
|
|
447
|
+
final_unit_test = ""
|
|
448
|
+
final_code = ""
|
|
449
|
+
try:
|
|
450
|
+
with open(unit_test_file, "r") as f:
|
|
451
|
+
final_unit_test = f.read()
|
|
452
|
+
except Exception:
|
|
453
|
+
pass
|
|
454
|
+
try:
|
|
455
|
+
with open(code_file, "r") as f:
|
|
456
|
+
final_code = f.read()
|
|
457
|
+
except Exception:
|
|
458
|
+
pass
|
|
459
|
+
return success, final_unit_test, final_code, 1, agent_cost, agent_model
|
|
460
|
+
else:
|
|
461
|
+
# Agentic fallback disabled, return failure
|
|
462
|
+
return False, "", "", fix_attempts, total_cost, model_name
|
|
250
463
|
|
|
251
464
|
# If target is not a Python file, trigger agentic fallback if tests fail
|
|
252
465
|
if not is_python:
|
|
@@ -263,7 +476,7 @@ def fix_error_loop(unit_test_file: str,
|
|
|
263
476
|
code_file=code_file,
|
|
264
477
|
unit_test_file=unit_test_file,
|
|
265
478
|
error_log_file=error_log_file,
|
|
266
|
-
cwd=
|
|
479
|
+
cwd=None, # Use project root (cwd), not prompt file's parent
|
|
267
480
|
)
|
|
268
481
|
if not success:
|
|
269
482
|
rprint(f"[bold red]Agentic fix fallback failed: {agent_msg}[/bold red]")
|
|
@@ -383,17 +596,18 @@ def fix_error_loop(unit_test_file: str,
|
|
|
383
596
|
break
|
|
384
597
|
|
|
385
598
|
# We only attempt to fix if test is failing or has warnings:
|
|
386
|
-
# Let's create backups
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
)
|
|
599
|
+
# Let's create backups in .pdd/backups/ to avoid polluting code/test directories
|
|
600
|
+
code_name = os.path.basename(code_file)
|
|
601
|
+
code_basename = os.path.splitext(code_name)[0]
|
|
602
|
+
unit_test_name = os.path.basename(unit_test_file)
|
|
603
|
+
unit_test_ext = os.path.splitext(unit_test_name)[1]
|
|
604
|
+
code_ext = os.path.splitext(code_name)[1]
|
|
605
|
+
|
|
606
|
+
backup_dir = Path.cwd() / '.pdd' / 'backups' / code_basename / timestamp
|
|
607
|
+
backup_dir.mkdir(parents=True, exist_ok=True)
|
|
608
|
+
|
|
609
|
+
unit_test_backup = str(backup_dir / f"test_{iteration}_{errors}_{fails}_{warnings}{unit_test_ext}")
|
|
610
|
+
code_backup = str(backup_dir / f"code_{iteration}_{errors}_{fails}_{warnings}{code_ext}")
|
|
397
611
|
try:
|
|
398
612
|
shutil.copy(unit_test_file, unit_test_backup)
|
|
399
613
|
shutil.copy(code_file, code_backup)
|
|
@@ -402,7 +616,8 @@ def fix_error_loop(unit_test_file: str,
|
|
|
402
616
|
rprint(f"[green]Created backup for code file:[/green] {code_backup}")
|
|
403
617
|
except Exception as e:
|
|
404
618
|
rprint(f"[red]Error creating backup files:[/red] {e}")
|
|
405
|
-
|
|
619
|
+
success = False
|
|
620
|
+
break # Exit loop but continue to agentic fallback (Issue #266)
|
|
406
621
|
|
|
407
622
|
# Update best iteration if needed:
|
|
408
623
|
if (errors < best_iteration_info["errors"] or
|
|
@@ -425,30 +640,67 @@ def fix_error_loop(unit_test_file: str,
|
|
|
425
640
|
code_contents = f.read()
|
|
426
641
|
except Exception as e:
|
|
427
642
|
rprint(f"[red]Error reading input files:[/red] {e}")
|
|
428
|
-
|
|
643
|
+
success = False
|
|
644
|
+
break # Exit loop but continue to agentic fallback (Issue #266)
|
|
429
645
|
|
|
430
|
-
# Call fix:
|
|
646
|
+
# Call fix (cloud or local based on use_cloud parameter):
|
|
431
647
|
try:
|
|
432
|
-
# Format the log for the LLM
|
|
648
|
+
# Format the log for the LLM - includes local test results
|
|
433
649
|
formatted_log = format_log_for_output(log_structure)
|
|
434
650
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
651
|
+
if use_cloud:
|
|
652
|
+
# Use cloud LLM for fix - local test results passed via formatted_log
|
|
653
|
+
try:
|
|
654
|
+
updated_unit_test, updated_code, fixed_unit_test, fixed_code, analysis, cost, model_name = cloud_fix_errors(
|
|
655
|
+
unit_test=unit_test_contents,
|
|
656
|
+
code=code_contents,
|
|
657
|
+
prompt=prompt,
|
|
658
|
+
error=formatted_log, # Pass local test results to cloud
|
|
659
|
+
error_file=error_log_file,
|
|
660
|
+
strength=strength,
|
|
661
|
+
temperature=temperature,
|
|
662
|
+
verbose=verbose,
|
|
663
|
+
time=time,
|
|
664
|
+
code_file_ext=os.path.splitext(code_file)[1]
|
|
665
|
+
)
|
|
666
|
+
except RuntimeError as cloud_err:
|
|
667
|
+
# Cloud failed - fall back to local if it's a recoverable error
|
|
668
|
+
if "Insufficient credits" in str(cloud_err) or "Authentication failed" in str(cloud_err) or "Access denied" in str(cloud_err):
|
|
669
|
+
# Non-recoverable errors - stop the loop
|
|
670
|
+
rprint(f"[red]Cloud fix error (non-recoverable):[/red] {cloud_err}")
|
|
671
|
+
break
|
|
672
|
+
# Recoverable errors - fall back to local
|
|
673
|
+
rprint(f"[yellow]Cloud fix failed, falling back to local:[/yellow] {cloud_err}")
|
|
674
|
+
updated_unit_test, updated_code, fixed_unit_test, fixed_code, analysis, cost, model_name = fix_errors_from_unit_tests(
|
|
675
|
+
unit_test_contents,
|
|
676
|
+
code_contents,
|
|
677
|
+
prompt,
|
|
678
|
+
formatted_log,
|
|
679
|
+
error_log_file,
|
|
680
|
+
strength,
|
|
681
|
+
temperature,
|
|
682
|
+
verbose=verbose,
|
|
683
|
+
time=time
|
|
684
|
+
)
|
|
685
|
+
else:
|
|
686
|
+
# Use local LLM for fix
|
|
687
|
+
updated_unit_test, updated_code, fixed_unit_test, fixed_code, analysis, cost, model_name = fix_errors_from_unit_tests(
|
|
688
|
+
unit_test_contents,
|
|
689
|
+
code_contents,
|
|
690
|
+
prompt,
|
|
691
|
+
formatted_log, # Use formatted log instead of reading the file
|
|
692
|
+
error_log_file,
|
|
693
|
+
strength,
|
|
694
|
+
temperature,
|
|
695
|
+
verbose=verbose,
|
|
696
|
+
time=time # Pass time parameter
|
|
697
|
+
)
|
|
698
|
+
|
|
447
699
|
# Update the fix attempt in the structured log
|
|
448
700
|
log_structure["iterations"][-1]["fix_attempt"] = analysis
|
|
449
701
|
log_structure["iterations"][-1]["model_name"] = model_name
|
|
450
702
|
except Exception as e:
|
|
451
|
-
rprint(f"[red]Error during
|
|
703
|
+
rprint(f"[red]Error during fix call:[/red] {e}")
|
|
452
704
|
break
|
|
453
705
|
|
|
454
706
|
fix_attempts += 1 # We used one fix attempt
|
|
@@ -544,7 +796,8 @@ def fix_error_loop(unit_test_file: str,
|
|
|
544
796
|
stats["final_warnings"] = warnings
|
|
545
797
|
except Exception as e:
|
|
546
798
|
rprint(f"[red]Error running pytest for next iteration:[/red] {e}")
|
|
547
|
-
|
|
799
|
+
success = False
|
|
800
|
+
break # Exit loop but continue to agentic fallback (Issue #266)
|
|
548
801
|
|
|
549
802
|
# Possibly restore best iteration if the final run is not as good:
|
|
550
803
|
if best_iteration_info["attempt"] is not None and not success:
|
|
@@ -643,7 +896,7 @@ def fix_error_loop(unit_test_file: str,
|
|
|
643
896
|
code_file=code_file,
|
|
644
897
|
unit_test_file=unit_test_file,
|
|
645
898
|
error_log_file=error_log_file,
|
|
646
|
-
cwd=
|
|
899
|
+
cwd=None, # Use project root (cwd), not prompt file's parent
|
|
647
900
|
)
|
|
648
901
|
total_cost += agent_cost
|
|
649
902
|
if not agent_success:
|