pdd-cli 0.0.90__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 +38 -6
- 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 +521 -786
- pdd/agentic_e2e_fix.py +319 -0
- pdd/agentic_e2e_fix_orchestrator.py +426 -0
- pdd/agentic_fix.py +118 -3
- pdd/agentic_update.py +25 -8
- pdd/architecture_sync.py +565 -0
- pdd/auth_service.py +210 -0
- pdd/auto_deps_main.py +63 -53
- pdd/auto_include.py +185 -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 +87 -29
- pdd/commands/auth.py +309 -0
- pdd/commands/connect.py +290 -0
- pdd/commands/fix.py +136 -113
- pdd/commands/maintenance.py +3 -2
- pdd/commands/misc.py +8 -0
- pdd/commands/modify.py +190 -164
- pdd/commands/sessions.py +284 -0
- 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 +27 -3
- pdd/core/cloud.py +237 -0
- 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 +204 -4
- 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-DQ3wkeQ2.js +449 -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 +459 -95
- pdd/load_prompt_template.py +15 -34
- pdd/path_resolution.py +140 -0
- pdd/postprocess.py +4 -1
- 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 +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_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 +20 -1
- pdd/prompts/generate_test_from_example_LLM.prompt +115 -0
- pdd/prompts/insert_includes_LLM.prompt +262 -252
- pdd/prompts/prompt_code_diff_LLM.prompt +119 -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 +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/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 +136 -75
- 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 +23 -5
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.118.dist-info}/METADATA +15 -10
- pdd_cli-0.0.118.dist-info/RECORD +227 -0
- pdd_cli-0.0.90.dist-info/RECORD +0 -153
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.118.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.118.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.118.dist-info}/licenses/LICENSE +0 -0
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.118.dist-info}/top_level.txt +0 -0
pdd/code_generator_main.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import re
|
|
3
|
-
import asyncio
|
|
4
3
|
import json
|
|
5
4
|
import pathlib
|
|
6
5
|
import shlex
|
|
@@ -21,16 +20,15 @@ from .construct_paths import construct_paths
|
|
|
21
20
|
from .preprocess import preprocess as pdd_preprocess
|
|
22
21
|
from .code_generator import code_generator as local_code_generator_func
|
|
23
22
|
from .incremental_code_generator import incremental_code_generator as incremental_code_generator_func
|
|
24
|
-
from .
|
|
23
|
+
from .core.cloud import CloudConfig
|
|
25
24
|
from .python_env_detector import detect_host_python_executable
|
|
25
|
+
from .architecture_sync import (
|
|
26
|
+
get_architecture_entry_for_prompt,
|
|
27
|
+
has_pdd_tags,
|
|
28
|
+
generate_tags_from_architecture,
|
|
29
|
+
)
|
|
26
30
|
|
|
27
|
-
#
|
|
28
|
-
FIREBASE_API_KEY_ENV_VAR = "NEXT_PUBLIC_FIREBASE_API_KEY"
|
|
29
|
-
GITHUB_CLIENT_ID_ENV_VAR = "GITHUB_CLIENT_ID"
|
|
30
|
-
PDD_APP_NAME = "PDD Code Generator"
|
|
31
|
-
|
|
32
|
-
# Cloud function URL
|
|
33
|
-
CLOUD_GENERATE_URL = "https://us-central1-prompt-driven-development.cloudfunctions.net/generateCode"
|
|
31
|
+
# Cloud request timeout
|
|
34
32
|
CLOUD_REQUEST_TIMEOUT = 400 # seconds
|
|
35
33
|
|
|
36
34
|
console = Console()
|
|
@@ -46,6 +44,13 @@ def _parse_llm_bool(value: str) -> bool:
|
|
|
46
44
|
else:
|
|
47
45
|
return llm_str in {"1", "true", "yes", "on"}
|
|
48
46
|
|
|
47
|
+
def _env_flag_enabled(name: str) -> bool:
|
|
48
|
+
"""Return True when an env var is set to a truthy value."""
|
|
49
|
+
value = os.environ.get(name)
|
|
50
|
+
if value is None:
|
|
51
|
+
return False
|
|
52
|
+
return str(value).strip().lower() in {"1", "true", "yes", "on"}
|
|
53
|
+
|
|
49
54
|
# --- Git Helper Functions ---
|
|
50
55
|
def _run_git_command(command: List[str], cwd: Optional[str] = None) -> Tuple[int, str, str]:
|
|
51
56
|
"""Runs a git command and returns (return_code, stdout, stderr)."""
|
|
@@ -762,7 +767,8 @@ def code_generator_main(
|
|
|
762
767
|
if verbose:
|
|
763
768
|
console.print(Panel("Performing full code generation...", title="[blue]Mode[/blue]", expand=False))
|
|
764
769
|
|
|
765
|
-
|
|
770
|
+
cloud_only = _env_flag_enabled("PDD_CLOUD_ONLY") or _env_flag_enabled("PDD_NO_LOCAL_FALLBACK")
|
|
771
|
+
current_execution_is_local = is_local_execution_preferred and not cloud_only
|
|
766
772
|
|
|
767
773
|
if not current_execution_is_local:
|
|
768
774
|
if verbose: console.print("Attempting cloud code generation...")
|
|
@@ -772,31 +778,22 @@ def code_generator_main(
|
|
|
772
778
|
processed_prompt_for_cloud = pdd_preprocess(processed_prompt_for_cloud, recursive=False, double_curly_brackets=True, exclude_keys=[])
|
|
773
779
|
if verbose: console.print(Panel(Text(processed_prompt_for_cloud, overflow="fold"), title="[cyan]Preprocessed Prompt for Cloud[/cyan]", expand=False))
|
|
774
780
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
jwt_token = asyncio.run(get_jwt_token(
|
|
784
|
-
firebase_api_key=firebase_api_key_val,
|
|
785
|
-
github_client_id=github_client_id_val,
|
|
786
|
-
app_name=PDD_APP_NAME
|
|
787
|
-
))
|
|
788
|
-
except (AuthError, NetworkError, TokenError, UserCancelledError, RateLimitError) as e:
|
|
789
|
-
console.print(f"[yellow]Cloud authentication/token error: {e}. Falling back to local execution.[/yellow]")
|
|
790
|
-
current_execution_is_local = True
|
|
791
|
-
except Exception as e:
|
|
792
|
-
console.print(f"[yellow]Unexpected error during cloud authentication: {e}. Falling back to local execution.[/yellow]")
|
|
781
|
+
# Get JWT token via CloudConfig (handles both injected tokens and device flow)
|
|
782
|
+
jwt_token = CloudConfig.get_jwt_token(verbose=verbose)
|
|
783
|
+
|
|
784
|
+
if not jwt_token:
|
|
785
|
+
if cloud_only:
|
|
786
|
+
console.print("[red]Cloud authentication failed.[/red]")
|
|
787
|
+
raise click.UsageError("Cloud authentication failed")
|
|
788
|
+
console.print("[yellow]Cloud authentication failed. Falling back to local execution.[/yellow]")
|
|
793
789
|
current_execution_is_local = True
|
|
794
790
|
|
|
795
791
|
if jwt_token and not current_execution_is_local:
|
|
796
792
|
payload = {"promptContent": processed_prompt_for_cloud, "language": language, "strength": strength, "temperature": temperature, "verbose": verbose}
|
|
797
793
|
headers = {"Authorization": f"Bearer {jwt_token}", "Content-Type": "application/json"}
|
|
794
|
+
cloud_url = CloudConfig.get_endpoint_url("generateCode")
|
|
798
795
|
try:
|
|
799
|
-
response = requests.post(
|
|
796
|
+
response = requests.post(cloud_url, json=payload, headers=headers, timeout=CLOUD_REQUEST_TIMEOUT)
|
|
800
797
|
response.raise_for_status()
|
|
801
798
|
|
|
802
799
|
response_data = response.json()
|
|
@@ -804,22 +801,71 @@ def code_generator_main(
|
|
|
804
801
|
total_cost = float(response_data.get("totalCost", 0.0))
|
|
805
802
|
model_name = response_data.get("modelName", "cloud_model")
|
|
806
803
|
|
|
804
|
+
# Strip markdown code fences if present (cloud API returns fenced JSON)
|
|
805
|
+
if generated_code_content and isinstance(language, str) and language.strip().lower() == "json":
|
|
806
|
+
cleaned = generated_code_content.strip()
|
|
807
|
+
if cleaned.startswith("```json"):
|
|
808
|
+
cleaned = cleaned[7:]
|
|
809
|
+
elif cleaned.startswith("```"):
|
|
810
|
+
cleaned = cleaned[3:]
|
|
811
|
+
if cleaned.endswith("```"):
|
|
812
|
+
cleaned = cleaned[:-3]
|
|
813
|
+
generated_code_content = cleaned.strip()
|
|
814
|
+
|
|
807
815
|
if not generated_code_content:
|
|
816
|
+
if cloud_only:
|
|
817
|
+
console.print("[red]Cloud execution returned no code.[/red]")
|
|
818
|
+
raise click.UsageError("Cloud execution returned no code")
|
|
808
819
|
console.print("[yellow]Cloud execution returned no code. Falling back to local.[/yellow]")
|
|
809
820
|
current_execution_is_local = True
|
|
810
821
|
elif verbose:
|
|
811
822
|
console.print(Panel(f"Cloud generation successful. Model: {model_name}, Cost: ${total_cost:.6f}", title="[green]Cloud Success[/green]", expand=False))
|
|
812
823
|
except requests.exceptions.Timeout:
|
|
824
|
+
if cloud_only:
|
|
825
|
+
console.print(f"[red]Cloud execution timed out ({CLOUD_REQUEST_TIMEOUT}s).[/red]")
|
|
826
|
+
raise click.UsageError("Cloud execution timed out")
|
|
813
827
|
console.print(f"[yellow]Cloud execution timed out ({CLOUD_REQUEST_TIMEOUT}s). Falling back to local.[/yellow]")
|
|
814
828
|
current_execution_is_local = True
|
|
815
829
|
except requests.exceptions.HTTPError as e:
|
|
830
|
+
status_code = e.response.status_code if e.response else 0
|
|
816
831
|
err_content = e.response.text[:200] if e.response else "No response content"
|
|
817
|
-
|
|
818
|
-
|
|
832
|
+
|
|
833
|
+
# Non-recoverable errors: do NOT fall back to local
|
|
834
|
+
if status_code == 402: # Insufficient credits
|
|
835
|
+
try:
|
|
836
|
+
error_data = e.response.json()
|
|
837
|
+
current_balance = error_data.get("currentBalance", "unknown")
|
|
838
|
+
estimated_cost = error_data.get("estimatedCost", "unknown")
|
|
839
|
+
console.print(f"[red]Insufficient credits. Current balance: {current_balance}, estimated cost: {estimated_cost}[/red]")
|
|
840
|
+
except Exception:
|
|
841
|
+
console.print(f"[red]Insufficient credits: {err_content}[/red]")
|
|
842
|
+
raise click.UsageError("Insufficient credits for cloud code generation")
|
|
843
|
+
elif status_code == 401: # Authentication error
|
|
844
|
+
console.print(f"[red]Authentication failed: {err_content}[/red]")
|
|
845
|
+
raise click.UsageError("Cloud authentication failed")
|
|
846
|
+
elif status_code == 403: # Authorization error (not approved)
|
|
847
|
+
console.print(f"[red]Access denied: {err_content}[/red]")
|
|
848
|
+
raise click.UsageError("Access denied - user not approved")
|
|
849
|
+
elif status_code == 400: # Validation error (e.g., empty prompt)
|
|
850
|
+
console.print(f"[red]Invalid request: {err_content}[/red]")
|
|
851
|
+
raise click.UsageError(f"Invalid request: {err_content}")
|
|
852
|
+
else:
|
|
853
|
+
# Recoverable errors (5xx, unexpected errors): fall back to local
|
|
854
|
+
if cloud_only:
|
|
855
|
+
console.print(f"[red]Cloud HTTP error ({status_code}): {err_content}[/red]")
|
|
856
|
+
raise click.UsageError(f"Cloud HTTP error ({status_code}): {err_content}")
|
|
857
|
+
console.print(f"[yellow]Cloud HTTP error ({status_code}): {err_content}. Falling back to local.[/yellow]")
|
|
858
|
+
current_execution_is_local = True
|
|
819
859
|
except requests.exceptions.RequestException as e:
|
|
860
|
+
if cloud_only:
|
|
861
|
+
console.print(f"[red]Cloud network error: {e}[/red]")
|
|
862
|
+
raise click.UsageError(f"Cloud network error: {e}")
|
|
820
863
|
console.print(f"[yellow]Cloud network error: {e}. Falling back to local.[/yellow]")
|
|
821
864
|
current_execution_is_local = True
|
|
822
865
|
except json.JSONDecodeError:
|
|
866
|
+
if cloud_only:
|
|
867
|
+
console.print("[red]Cloud returned invalid JSON.[/red]")
|
|
868
|
+
raise click.UsageError("Cloud returned invalid JSON")
|
|
823
869
|
console.print("[yellow]Cloud returned invalid JSON. Falling back to local.[/yellow]")
|
|
824
870
|
current_execution_is_local = True
|
|
825
871
|
|
|
@@ -1006,6 +1052,23 @@ def code_generator_main(
|
|
|
1006
1052
|
raise click.UsageError(f"LLM generation failed: {generated_code_content}")
|
|
1007
1053
|
|
|
1008
1054
|
parsed = json.loads(generated_code_content)
|
|
1055
|
+
|
|
1056
|
+
# Fix common LLM mistake: unwrap arrays wrapped in objects
|
|
1057
|
+
# LLMs often return {"items": [...]} or {"type": "array", "items": [...]}
|
|
1058
|
+
# when the schema expects a plain array [...]
|
|
1059
|
+
output_schema = fm_meta.get("output_schema", {})
|
|
1060
|
+
if output_schema.get("type") == "array" and isinstance(parsed, dict):
|
|
1061
|
+
# Check for common wrapper patterns
|
|
1062
|
+
if "items" in parsed and isinstance(parsed["items"], list):
|
|
1063
|
+
parsed = parsed["items"]
|
|
1064
|
+
generated_code_content = json.dumps(parsed, indent=2)
|
|
1065
|
+
elif "data" in parsed and isinstance(parsed["data"], list):
|
|
1066
|
+
parsed = parsed["data"]
|
|
1067
|
+
generated_code_content = json.dumps(parsed, indent=2)
|
|
1068
|
+
elif "results" in parsed and isinstance(parsed["results"], list):
|
|
1069
|
+
parsed = parsed["results"]
|
|
1070
|
+
generated_code_content = json.dumps(parsed, indent=2)
|
|
1071
|
+
|
|
1009
1072
|
if _is_architecture_template(fm_meta):
|
|
1010
1073
|
parsed, repaired = _repair_architecture_interface_types(parsed)
|
|
1011
1074
|
if repaired:
|
|
@@ -1024,7 +1087,30 @@ def code_generator_main(
|
|
|
1024
1087
|
if output_path:
|
|
1025
1088
|
p_output = pathlib.Path(output_path)
|
|
1026
1089
|
p_output.parent.mkdir(parents=True, exist_ok=True)
|
|
1027
|
-
|
|
1090
|
+
|
|
1091
|
+
# Inject architecture metadata tags for .prompt files (reverse sync)
|
|
1092
|
+
final_content = generated_code_content
|
|
1093
|
+
if p_output.suffix == '.prompt':
|
|
1094
|
+
try:
|
|
1095
|
+
# Check if this prompt has an architecture entry
|
|
1096
|
+
arch_entry = get_architecture_entry_for_prompt(p_output.name)
|
|
1097
|
+
|
|
1098
|
+
# Only inject tags if:
|
|
1099
|
+
# 1. Architecture entry exists
|
|
1100
|
+
# 2. Content doesn't already have PDD tags (preserve manual edits)
|
|
1101
|
+
if arch_entry and not has_pdd_tags(generated_code_content):
|
|
1102
|
+
tags = generate_tags_from_architecture(arch_entry)
|
|
1103
|
+
if tags:
|
|
1104
|
+
# Prepend tags to the generated content
|
|
1105
|
+
final_content = tags + '\n\n' + generated_code_content
|
|
1106
|
+
if verbose:
|
|
1107
|
+
console.print("[info]Injected architecture metadata tags from architecture.json[/info]")
|
|
1108
|
+
except Exception as e:
|
|
1109
|
+
# Don't fail generation if tag injection fails
|
|
1110
|
+
if verbose:
|
|
1111
|
+
console.print(f"[yellow]Warning: Could not inject architecture tags: {e}[/yellow]")
|
|
1112
|
+
|
|
1113
|
+
p_output.write_text(final_content, encoding="utf-8")
|
|
1028
1114
|
if verbose or not quiet:
|
|
1029
1115
|
console.print(f"Generated code saved to: [green]{p_output.resolve()}[/green]")
|
|
1030
1116
|
# Safety net: ensure architecture HTML is generated post-write if applicable
|
pdd/commands/__init__.py
CHANGED
|
@@ -8,7 +8,10 @@ from .fix import fix
|
|
|
8
8
|
from .modify import split, change, update
|
|
9
9
|
from .maintenance import sync, auto_deps, setup
|
|
10
10
|
from .analysis import detect_change, conflicts, bug, crash, trace
|
|
11
|
+
from .connect import connect
|
|
12
|
+
from .auth import auth_group
|
|
11
13
|
from .misc import preprocess
|
|
14
|
+
from .sessions import sessions
|
|
12
15
|
from .report import report_core
|
|
13
16
|
from .templates import templates_group
|
|
14
17
|
from .utility import install_completion_cmd, verify
|
|
@@ -40,3 +43,6 @@ def register_commands(cli: click.Group) -> None:
|
|
|
40
43
|
# The original code did: cli.commands["templates"] = templates_group
|
|
41
44
|
# Using add_command is cleaner if it works for the structure.
|
|
42
45
|
cli.add_command(templates_group)
|
|
46
|
+
cli.add_command(connect)
|
|
47
|
+
cli.add_command(auth_group)
|
|
48
|
+
cli.add_command(sessions)
|
pdd/commands/analysis.py
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
Analysis commands (detect-change, conflicts, bug, crash, trace).
|
|
3
5
|
"""
|
|
6
|
+
import os
|
|
4
7
|
import click
|
|
5
8
|
from typing import Optional, Tuple, List
|
|
6
9
|
|
|
7
10
|
from ..detect_change_main import detect_change_main
|
|
8
11
|
from ..conflicts_main import conflicts_main
|
|
9
12
|
from ..bug_main import bug_main
|
|
13
|
+
from ..agentic_bug import run_agentic_bug
|
|
10
14
|
from ..crash_main import crash_main
|
|
11
15
|
from ..trace_main import trace_main
|
|
12
16
|
from ..track_cost import track_cost
|
|
@@ -46,7 +50,7 @@ def detect_change(
|
|
|
46
50
|
output=output,
|
|
47
51
|
)
|
|
48
52
|
return result, total_cost, model_name
|
|
49
|
-
except click.Abort:
|
|
53
|
+
except (click.Abort, click.ClickException):
|
|
50
54
|
raise
|
|
51
55
|
except Exception as exception:
|
|
52
56
|
handle_error(exception, "detect", ctx.obj.get("quiet", False))
|
|
@@ -80,7 +84,7 @@ def conflicts(
|
|
|
80
84
|
verbose=ctx.obj.get("verbose", False),
|
|
81
85
|
)
|
|
82
86
|
return result, total_cost, model_name
|
|
83
|
-
except click.Abort:
|
|
87
|
+
except (click.Abort, click.ClickException):
|
|
84
88
|
raise
|
|
85
89
|
except Exception as exception:
|
|
86
90
|
handle_error(exception, "conflicts", ctx.obj.get("quiet", False))
|
|
@@ -88,11 +92,13 @@ def conflicts(
|
|
|
88
92
|
|
|
89
93
|
|
|
90
94
|
@click.command("bug")
|
|
91
|
-
@click.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
@click.option(
|
|
96
|
+
"--manual",
|
|
97
|
+
is_flag=True,
|
|
98
|
+
default=False,
|
|
99
|
+
help="Run in manual mode requiring 5 positional file arguments.",
|
|
100
|
+
)
|
|
101
|
+
@click.argument("args", nargs=-1)
|
|
96
102
|
@click.option(
|
|
97
103
|
"--output",
|
|
98
104
|
type=click.Path(writable=True),
|
|
@@ -103,34 +109,86 @@ def conflicts(
|
|
|
103
109
|
"--language",
|
|
104
110
|
type=str,
|
|
105
111
|
default="Python",
|
|
106
|
-
help="Programming language for the unit test.",
|
|
112
|
+
help="Programming language for the unit test (Manual mode only).",
|
|
113
|
+
)
|
|
114
|
+
@click.option(
|
|
115
|
+
"--timeout-adder",
|
|
116
|
+
type=float,
|
|
117
|
+
default=0.0,
|
|
118
|
+
help="Additional seconds to add to each step's timeout (agentic mode only).",
|
|
119
|
+
)
|
|
120
|
+
@click.option(
|
|
121
|
+
"--no-github-state",
|
|
122
|
+
is_flag=True,
|
|
123
|
+
default=False,
|
|
124
|
+
help="Disable GitHub state persistence (agentic mode only).",
|
|
107
125
|
)
|
|
108
126
|
@click.pass_context
|
|
109
127
|
@track_cost
|
|
110
128
|
def bug(
|
|
111
129
|
ctx: click.Context,
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
program_file: str,
|
|
115
|
-
current_output: str,
|
|
116
|
-
desired_output: str,
|
|
130
|
+
manual: bool,
|
|
131
|
+
args: Tuple[str, ...],
|
|
117
132
|
output: Optional[str],
|
|
118
133
|
language: str,
|
|
134
|
+
timeout_adder: float,
|
|
135
|
+
no_github_state: bool,
|
|
119
136
|
) -> Optional[Tuple[str, float, str]]:
|
|
120
|
-
"""Generate a unit test
|
|
137
|
+
"""Generate a unit test (manual) or investigate a bug (agentic).
|
|
138
|
+
|
|
139
|
+
Agentic Mode (default):
|
|
140
|
+
pdd bug ISSUE_URL
|
|
141
|
+
|
|
142
|
+
Manual Mode:
|
|
143
|
+
pdd bug --manual PROMPT_FILE CODE_FILE PROGRAM_FILE CURRENT_OUTPUT DESIRED_OUTPUT
|
|
144
|
+
"""
|
|
121
145
|
try:
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
146
|
+
if manual:
|
|
147
|
+
if len(args) != 5:
|
|
148
|
+
raise click.UsageError(
|
|
149
|
+
"Manual mode requires 5 arguments: PROMPT_FILE CODE_FILE PROGRAM_FILE CURRENT_OUTPUT DESIRED_OUTPUT"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Validate files exist (replicating click.Path(exists=True))
|
|
153
|
+
for f in args:
|
|
154
|
+
if not os.path.exists(f):
|
|
155
|
+
raise click.UsageError(f"File does not exist: {f}")
|
|
156
|
+
if os.path.isdir(f):
|
|
157
|
+
raise click.UsageError(f"Path is a directory, not a file: {f}")
|
|
158
|
+
|
|
159
|
+
prompt_file, code_file, program_file, current_output, desired_output = args
|
|
160
|
+
|
|
161
|
+
result, total_cost, model_name = bug_main(
|
|
162
|
+
ctx=ctx,
|
|
163
|
+
prompt_file=prompt_file,
|
|
164
|
+
code_file=code_file,
|
|
165
|
+
program_file=program_file,
|
|
166
|
+
current_output=current_output,
|
|
167
|
+
desired_output=desired_output,
|
|
168
|
+
output=output,
|
|
169
|
+
language=language,
|
|
170
|
+
)
|
|
171
|
+
return result, total_cost, model_name
|
|
172
|
+
|
|
173
|
+
else:
|
|
174
|
+
# Agentic mode
|
|
175
|
+
if len(args) != 1:
|
|
176
|
+
raise click.UsageError("Agentic mode requires exactly one argument: the GitHub Issue URL.")
|
|
177
|
+
|
|
178
|
+
issue_url = args[0]
|
|
179
|
+
|
|
180
|
+
success, message, cost, model, changed_files = run_agentic_bug(
|
|
181
|
+
issue_url=issue_url,
|
|
182
|
+
verbose=ctx.obj.get("verbose", False),
|
|
183
|
+
quiet=ctx.obj.get("quiet", False),
|
|
184
|
+
timeout_adder=timeout_adder,
|
|
185
|
+
use_github_state=not no_github_state,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
result_str = f"Success: {success}\nMessage: {message}\nChanged Files: {changed_files}"
|
|
189
|
+
return result_str, cost, model
|
|
190
|
+
|
|
191
|
+
except (click.Abort, click.ClickException):
|
|
134
192
|
raise
|
|
135
193
|
except Exception as exception:
|
|
136
194
|
handle_error(exception, "bug", ctx.obj.get("quiet", False))
|
|
@@ -188,7 +246,7 @@ def crash(
|
|
|
188
246
|
) -> Optional[Tuple[str, float, str]]:
|
|
189
247
|
"""Analyze a crash and fix the code and program."""
|
|
190
248
|
try:
|
|
191
|
-
# crash_main returns: success, final_code, final_program, attempts,
|
|
249
|
+
# crash_main returns: success, final_code, final_program, attempts, total_cost, model_name
|
|
192
250
|
success, final_code, final_program, attempts, total_cost, model_name = crash_main(
|
|
193
251
|
ctx=ctx,
|
|
194
252
|
prompt_file=prompt_file,
|
|
@@ -204,7 +262,7 @@ def crash(
|
|
|
204
262
|
# Return a summary string as the result for track_cost/CLI output
|
|
205
263
|
result = f"Success: {success}, Attempts: {attempts}"
|
|
206
264
|
return result, total_cost, model_name
|
|
207
|
-
except click.Abort:
|
|
265
|
+
except (click.Abort, click.ClickException):
|
|
208
266
|
raise
|
|
209
267
|
except Exception as exception:
|
|
210
268
|
handle_error(exception, "crash", ctx.obj.get("quiet", False))
|
|
@@ -241,7 +299,7 @@ def trace(
|
|
|
241
299
|
output=output,
|
|
242
300
|
)
|
|
243
301
|
return str(result), total_cost, model_name
|
|
244
|
-
except click.Abort:
|
|
302
|
+
except (click.Abort, click.ClickException):
|
|
245
303
|
raise
|
|
246
304
|
except Exception as exception:
|
|
247
305
|
handle_error(exception, "trace", ctx.obj.get("quiet", False))
|