pdd-cli 0.0.136__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 +52 -0
- pdd/agentic_architecture.py +270 -0
- pdd/agentic_architecture_orchestrator.py +676 -0
- pdd/agentic_bug.py +323 -0
- pdd/agentic_bug_orchestrator.py +567 -0
- pdd/agentic_change.py +242 -0
- pdd/agentic_change_orchestrator.py +829 -0
- pdd/agentic_common.py +849 -0
- pdd/agentic_crash.py +552 -0
- pdd/agentic_e2e_fix.py +322 -0
- pdd/agentic_e2e_fix_orchestrator.py +615 -0
- pdd/agentic_fix.py +451 -0
- pdd/agentic_langtest.py +59 -0
- pdd/agentic_test.py +308 -0
- pdd/agentic_test_generate.py +310 -0
- pdd/agentic_test_orchestrator.py +471 -0
- pdd/agentic_update.py +388 -0
- pdd/agentic_verify.py +184 -0
- pdd/architecture_sync.py +571 -0
- pdd/auth_service.py +398 -0
- pdd/auto_deps_main.py +124 -0
- pdd/auto_include.py +515 -0
- pdd/auto_update.py +226 -0
- pdd/bug_main.py +290 -0
- pdd/bug_to_unit_test.py +187 -0
- pdd/change.py +193 -0
- pdd/change_main.py +529 -0
- pdd/cli.py +43 -0
- pdd/cmd_test_main.py +426 -0
- pdd/code_generator.py +171 -0
- pdd/code_generator_main.py +1197 -0
- pdd/commands/__init__.py +50 -0
- pdd/commands/analysis.py +313 -0
- pdd/commands/auth.py +367 -0
- pdd/commands/connect.py +366 -0
- pdd/commands/fix.py +191 -0
- pdd/commands/generate.py +326 -0
- pdd/commands/maintenance.py +183 -0
- pdd/commands/misc.py +87 -0
- pdd/commands/modify.py +292 -0
- pdd/commands/report.py +149 -0
- pdd/commands/sessions.py +284 -0
- pdd/commands/templates.py +215 -0
- pdd/commands/utility.py +115 -0
- pdd/commands/which.py +297 -0
- pdd/comment_line.py +35 -0
- pdd/config_resolution.py +58 -0
- pdd/conflicts_in_prompts.py +149 -0
- pdd/conflicts_main.py +99 -0
- pdd/construct_paths.py +1288 -0
- pdd/context_generator.py +160 -0
- pdd/context_generator_main.py +209 -0
- pdd/continue_generation.py +192 -0
- pdd/core/__init__.py +33 -0
- pdd/core/cli.py +558 -0
- pdd/core/cloud.py +290 -0
- pdd/core/dump.py +602 -0
- pdd/core/errors.py +68 -0
- pdd/core/remote_session.py +61 -0
- pdd/core/utils.py +90 -0
- pdd/crash_main.py +406 -0
- pdd/data/language_format.csv +94 -0
- pdd/data/llm_model.csv +20 -0
- pdd/detect_change.py +147 -0
- pdd/detect_change_main.py +104 -0
- pdd/docs/prompting_guide.md +909 -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/edit_file.py +783 -0
- pdd/find_section.py +28 -0
- pdd/fix_code_loop.py +951 -0
- pdd/fix_code_module_errors.py +165 -0
- pdd/fix_error_loop.py +995 -0
- pdd/fix_errors_from_unit_tests.py +254 -0
- pdd/fix_main.py +563 -0
- pdd/fix_verification_errors.py +260 -0
- pdd/fix_verification_errors_loop.py +1288 -0
- pdd/fix_verification_main.py +689 -0
- pdd/frontend/dist/assets/index-B5DZHykP.css +1 -0
- pdd/frontend/dist/assets/index-mnK5Bbrq.js +441 -0
- pdd/frontend/dist/index.html +403 -0
- pdd/frontend/dist/logo.svg +33 -0
- pdd/generate_output_paths.py +697 -0
- pdd/generate_test.py +305 -0
- pdd/get_comment.py +30 -0
- pdd/get_extension.py +65 -0
- pdd/get_jwt_token.py +623 -0
- pdd/get_language.py +42 -0
- pdd/get_run_command.py +75 -0
- pdd/get_test_command.py +76 -0
- pdd/git_update.py +137 -0
- pdd/increase_tests.py +106 -0
- pdd/incremental_code_generator.py +198 -0
- pdd/insert_includes.py +166 -0
- pdd/install_completion.py +144 -0
- pdd/llm_invoke.py +3044 -0
- pdd/load_prompt_template.py +66 -0
- pdd/logo_animation.py +455 -0
- pdd/mcp_config.json +7 -0
- pdd/operation_log.py +351 -0
- pdd/path_resolution.py +140 -0
- pdd/pdd_completion.fish +157 -0
- pdd/pdd_completion.sh +186 -0
- pdd/pdd_completion.zsh +565 -0
- pdd/pin_example_hack.py +1753 -0
- pdd/postprocess.py +158 -0
- pdd/postprocess_0.py +52 -0
- pdd/preprocess.py +461 -0
- pdd/preprocess_main.py +114 -0
- pdd/process_csv_change.py +506 -0
- pdd/prompts/agentic_arch_step10_sync_LLM.prompt +224 -0
- pdd/prompts/agentic_arch_step11_deps_LLM.prompt +239 -0
- pdd/prompts/agentic_arch_step12_fix_LLM.prompt +5674 -0
- pdd/prompts/agentic_arch_step1_analyze_prd_LLM.prompt +93 -0
- pdd/prompts/agentic_arch_step2_analyze_LLM.prompt +103 -0
- pdd/prompts/agentic_arch_step3_research_LLM.prompt +1022 -0
- pdd/prompts/agentic_arch_step4_design_LLM.prompt +114 -0
- pdd/prompts/agentic_arch_step5_research_deps_LLM.prompt +98 -0
- pdd/prompts/agentic_arch_step6_generate_LLM.prompt +1035 -0
- pdd/prompts/agentic_arch_step7_pddrc_LLM.prompt +599 -0
- pdd/prompts/agentic_arch_step8_prompts_LLM.prompt +803 -0
- pdd/prompts/agentic_arch_step9_completeness_LLM.prompt +197 -0
- pdd/prompts/agentic_bug_step10_pr_LLM.prompt +185 -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_5_prompt_classification_LLM.prompt +1090 -0
- pdd/prompts/agentic_bug_step5_root_cause_LLM.prompt +123 -0
- pdd/prompts/agentic_bug_step6_test_plan_LLM.prompt +111 -0
- pdd/prompts/agentic_bug_step7_generate_LLM.prompt +176 -0
- pdd/prompts/agentic_bug_step8_verify_LLM.prompt +124 -0
- pdd/prompts/agentic_bug_step9_e2e_test_LLM.prompt +293 -0
- pdd/prompts/agentic_change_step10_architecture_update_LLM.prompt +1086 -0
- pdd/prompts/agentic_change_step11_identify_issues_LLM.prompt +1051 -0
- pdd/prompts/agentic_change_step12_fix_issues_LLM.prompt +1029 -0
- pdd/prompts/agentic_change_step13_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 +1026 -0
- pdd/prompts/agentic_change_step6_devunits_LLM.prompt +1222 -0
- pdd/prompts/agentic_change_step7_architecture_LLM.prompt +1100 -0
- pdd/prompts/agentic_change_step8_analyze_LLM.prompt +1086 -0
- pdd/prompts/agentic_change_step9_implement_LLM.prompt +1201 -0
- pdd/prompts/agentic_crash_explore_LLM.prompt +49 -0
- pdd/prompts/agentic_e2e_fix_step1_unit_tests_LLM.prompt +88 -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 +118 -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_nonpython_LLM.prompt +45 -0
- pdd/prompts/agentic_fix_primary_LLM.prompt +39 -0
- pdd/prompts/agentic_test_generate_LLM.prompt +67 -0
- pdd/prompts/agentic_test_step1_duplicate_LLM.prompt +73 -0
- pdd/prompts/agentic_test_step2_docs_LLM.prompt +95 -0
- pdd/prompts/agentic_test_step3_clarify_LLM.prompt +111 -0
- pdd/prompts/agentic_test_step4_detect_frontend_LLM.prompt +126 -0
- pdd/prompts/agentic_test_step5_test_plan_LLM.prompt +180 -0
- pdd/prompts/agentic_test_step6_generate_tests_LLM.prompt +210 -0
- pdd/prompts/agentic_test_step7_run_tests_LLM.prompt +164 -0
- pdd/prompts/agentic_test_step8_fix_iterate_LLM.prompt +169 -0
- pdd/prompts/agentic_test_step9_submit_pr_LLM.prompt +173 -0
- pdd/prompts/agentic_update_LLM.prompt +970 -0
- pdd/prompts/agentic_verify_explore_LLM.prompt +45 -0
- pdd/prompts/auto_include_LLM.prompt +249 -0
- pdd/prompts/bug_to_unit_test_LLM.prompt +17 -0
- pdd/prompts/change_LLM.prompt +3133 -0
- pdd/prompts/code_patcher_LLM.prompt +63 -0
- pdd/prompts/conflict_LLM.prompt +23 -0
- pdd/prompts/continue_generation_LLM.prompt +3 -0
- pdd/prompts/detect_change_LLM.prompt +1181 -0
- pdd/prompts/diff_analyzer_LLM.prompt +69 -0
- pdd/prompts/example_generator_LLM.prompt +31 -0
- pdd/prompts/extract_auto_include_LLM.prompt +6 -0
- pdd/prompts/extract_code_LLM.prompt +26 -0
- pdd/prompts/extract_conflict_LLM.prompt +19 -0
- pdd/prompts/extract_detect_change_LLM.prompt +19 -0
- pdd/prompts/extract_program_code_fix_LLM.prompt +29 -0
- pdd/prompts/extract_prompt_change_LLM.prompt +13 -0
- pdd/prompts/extract_prompt_split_LLM.prompt +12 -0
- pdd/prompts/extract_prompt_update_LLM.prompt +13 -0
- pdd/prompts/extract_promptline_LLM.prompt +17 -0
- pdd/prompts/extract_unit_code_fix_LLM.prompt +332 -0
- pdd/prompts/extract_xml_LLM.prompt +7 -0
- pdd/prompts/find_verification_errors_LLM.prompt +44 -0
- pdd/prompts/fix_code_module_errors_LLM.prompt +66 -0
- pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +40 -0
- pdd/prompts/fix_verification_errors_LLM.prompt +62 -0
- pdd/prompts/generate_test_LLM.prompt +60 -0
- pdd/prompts/generate_test_from_example_LLM.prompt +723 -0
- pdd/prompts/increase_tests_LLM.prompt +15 -0
- pdd/prompts/insert_includes_LLM.prompt +1215 -0
- pdd/prompts/prompt_code_diff_LLM.prompt +123 -0
- pdd/prompts/prompt_diff_LLM.prompt +82 -0
- pdd/prompts/split_LLM.prompt +34 -0
- pdd/prompts/summarize_file_LLM.prompt +11 -0
- pdd/prompts/sync_analysis_LLM.prompt +82 -0
- pdd/prompts/trace_LLM.prompt +33 -0
- pdd/prompts/trim_results_LLM.prompt +83 -0
- pdd/prompts/trim_results_start_LLM.prompt +45 -0
- pdd/prompts/unfinished_prompt_LLM.prompt +102 -0
- pdd/prompts/update_prompt_LLM.prompt +40 -0
- pdd/prompts/xml_convertor_LLM.prompt +3293 -0
- pdd/pytest_output.py +324 -0
- pdd/python_env_detector.py +151 -0
- pdd/remote_session.py +1020 -0
- pdd/render_mermaid.py +236 -0
- pdd/server/__init__.py +52 -0
- pdd/server/app.py +340 -0
- pdd/server/click_executor.py +587 -0
- pdd/server/executor.py +338 -0
- pdd/server/jobs.py +679 -0
- pdd/server/models.py +241 -0
- pdd/server/routes/__init__.py +31 -0
- pdd/server/routes/architecture.py +569 -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 +860 -0
- pdd/server/routes/prompts.py +1344 -0
- pdd/server/routes/websocket.py +473 -0
- pdd/server/security.py +243 -0
- pdd/server/terminal_spawner.py +277 -0
- pdd/server/token_counter.py +222 -0
- pdd/setup_tool.py +648 -0
- pdd/split.py +131 -0
- pdd/split_main.py +117 -0
- pdd/summarize_directory.py +321 -0
- pdd/sync_animation.py +647 -0
- pdd/sync_determine_operation.py +2251 -0
- pdd/sync_main.py +716 -0
- pdd/sync_orchestration.py +1914 -0
- pdd/sync_order.py +353 -0
- pdd/sync_tui.py +848 -0
- pdd/template_expander.py +161 -0
- pdd/template_registry.py +264 -0
- pdd/templates/architecture/architecture_json.prompt +303 -0
- pdd/templates/architecture/example_nextjs_task_notes.prompt +505 -0
- pdd/templates/architecture/pdd_path_construction_guide.prompt +362 -0
- pdd/templates/generic/generate_pddrc_YAML.prompt +213 -0
- pdd/templates/generic/generate_prompt.prompt +188 -0
- pdd/trace.py +295 -0
- pdd/trace_main.py +117 -0
- pdd/track_cost.py +179 -0
- pdd/unfinished_prompt.py +169 -0
- pdd/update_main.py +632 -0
- pdd/update_model_costs.py +446 -0
- pdd/update_prompt.py +134 -0
- pdd/xml_tagger.py +137 -0
- pdd_cli-0.0.136.dist-info/METADATA +325 -0
- pdd_cli-0.0.136.dist-info/RECORD +262 -0
- pdd_cli-0.0.136.dist-info/WHEEL +5 -0
- pdd_cli-0.0.136.dist-info/entry_points.txt +2 -0
- pdd_cli-0.0.136.dist-info/licenses/LICENSE +7 -0
- pdd_cli-0.0.136.dist-info/top_level.txt +1 -0
pdd/__init__.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""PDD - Prompt Driven Development"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
__version__ = "0.0.136"
|
|
6
|
+
|
|
7
|
+
# Strength parameter used for LLM extraction across the codebase
|
|
8
|
+
# Used in postprocessing, XML tagging, code generation, and other extraction
|
|
9
|
+
# operations. The module should have a large context window and be affordable.
|
|
10
|
+
EXTRACTION_STRENGTH = 0.5
|
|
11
|
+
|
|
12
|
+
DEFAULT_STRENGTH = 1.0
|
|
13
|
+
|
|
14
|
+
DEFAULT_TEMPERATURE = 0.0
|
|
15
|
+
|
|
16
|
+
DEFAULT_TIME = 0.25
|
|
17
|
+
|
|
18
|
+
# Public OAuth credentials for cloud mode
|
|
19
|
+
# These are safe to embed as they are public client identifiers:
|
|
20
|
+
# - Firebase API keys are designed to be public (client-side)
|
|
21
|
+
# - GitHub OAuth Client IDs are public (the secret stays server-side)
|
|
22
|
+
# Users still need to authenticate via GitHub OAuth to use cloud features.
|
|
23
|
+
_DEFAULT_FIREBASE_API_KEY = "AIzaSyC0w2jwRR82ZFgQs_YXJoEBqnnTH71X6BE"
|
|
24
|
+
_DEFAULT_GITHUB_CLIENT_ID = "Ov23liJ4eSm0y5W1L20u"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _setup_cloud_defaults() -> None:
|
|
28
|
+
"""Set up default cloud credentials if not already set.
|
|
29
|
+
|
|
30
|
+
Only sets defaults when:
|
|
31
|
+
1. Not running inside cloud environment (K_SERVICE or FUNCTIONS_EMULATOR)
|
|
32
|
+
2. Environment variables are not already set
|
|
33
|
+
|
|
34
|
+
This prevents infinite loops when cloud endpoints call CLI internally,
|
|
35
|
+
while providing a seamless experience for local users.
|
|
36
|
+
"""
|
|
37
|
+
# Skip if running in cloud environment to prevent infinite loops
|
|
38
|
+
if os.environ.get("K_SERVICE") or os.environ.get("FUNCTIONS_EMULATOR"):
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
# Set Firebase API key if not already set
|
|
42
|
+
if not os.environ.get("NEXT_PUBLIC_FIREBASE_API_KEY"):
|
|
43
|
+
os.environ["NEXT_PUBLIC_FIREBASE_API_KEY"] = _DEFAULT_FIREBASE_API_KEY
|
|
44
|
+
|
|
45
|
+
# Set GitHub Client ID if not already set
|
|
46
|
+
if not os.environ.get("GITHUB_CLIENT_ID"):
|
|
47
|
+
os.environ["GITHUB_CLIENT_ID"] = _DEFAULT_GITHUB_CLIENT_ID
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# Initialize cloud defaults on package import
|
|
51
|
+
_setup_cloud_defaults()
|
|
52
|
+
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI entry point for the agentic architecture workflow.
|
|
3
|
+
Detects GitHub issue URLs, fetches issue content and comments via `gh api`,
|
|
4
|
+
ensures repository context is available, then invokes the orchestrator.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import re
|
|
11
|
+
import shutil
|
|
12
|
+
import subprocess
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import List, Optional, Tuple
|
|
15
|
+
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
|
|
18
|
+
# Internal imports
|
|
19
|
+
from .agentic_architecture_orchestrator import run_agentic_architecture_orchestrator
|
|
20
|
+
|
|
21
|
+
console = Console()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _is_github_issue_url(url: str) -> bool:
|
|
25
|
+
"""
|
|
26
|
+
Detect if the string is a valid GitHub issue URL.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
url: The URL string to check.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
True if the URL matches the GitHub issue pattern, False otherwise.
|
|
33
|
+
"""
|
|
34
|
+
return _parse_github_url(url) is not None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _parse_github_url(url: str) -> Optional[Tuple[str, str, int]]:
|
|
38
|
+
"""
|
|
39
|
+
Extract owner, repo, and issue number from a GitHub URL.
|
|
40
|
+
|
|
41
|
+
Supports:
|
|
42
|
+
- https://github.com/{owner}/{repo}/issues/{number}
|
|
43
|
+
- https://www.github.com/{owner}/{repo}/issues/{number}
|
|
44
|
+
- github.com/{owner}/{repo}/issues/{number}
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
url: The URL string to parse.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Tuple of (owner, repo, issue_number) if successful, None otherwise.
|
|
51
|
+
"""
|
|
52
|
+
pattern = r"(?:https?://)?(?:www\.)?github\.com/([^/]+)/([^/]+)/issues/(\d+)"
|
|
53
|
+
match = re.search(pattern, url)
|
|
54
|
+
if match:
|
|
55
|
+
owner, repo, number = match.groups()
|
|
56
|
+
return owner, repo, int(number)
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _check_gh_cli() -> bool:
|
|
61
|
+
"""Check if gh CLI tool is available on the PATH."""
|
|
62
|
+
return shutil.which("gh") is not None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _run_gh_command(args: List[str]) -> Tuple[bool, str]:
|
|
66
|
+
"""
|
|
67
|
+
Run a gh command and return (success, stdout/stderr).
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
args: List of arguments to pass to the gh command.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Tuple of (success boolean, output string).
|
|
74
|
+
"""
|
|
75
|
+
try:
|
|
76
|
+
result = subprocess.run(
|
|
77
|
+
["gh"] + args,
|
|
78
|
+
capture_output=True,
|
|
79
|
+
text=True,
|
|
80
|
+
check=True
|
|
81
|
+
)
|
|
82
|
+
return True, result.stdout
|
|
83
|
+
except subprocess.CalledProcessError as e:
|
|
84
|
+
return False, e.stderr or str(e)
|
|
85
|
+
except FileNotFoundError:
|
|
86
|
+
return False, "gh CLI not found"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _ensure_repo_context(owner: str, repo: str, current_cwd: Path, quiet: bool) -> Tuple[Path, Optional[str]]:
|
|
90
|
+
"""
|
|
91
|
+
Ensure the repository is available locally.
|
|
92
|
+
|
|
93
|
+
Logic:
|
|
94
|
+
1. If current_cwd is inside the target repo (checked via remote), use it.
|
|
95
|
+
2. If current_cwd is a git repo but mismatch, warn and use it (user might be in a fork).
|
|
96
|
+
3. If current_cwd is NOT a git repo:
|
|
97
|
+
a. Check if subdirectory {repo} exists and is a git repo -> use it.
|
|
98
|
+
b. Clone {owner}/{repo} -> use it.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
owner: Repository owner.
|
|
102
|
+
repo: Repository name.
|
|
103
|
+
current_cwd: Current working directory.
|
|
104
|
+
quiet: Whether to suppress non-error output.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Tuple of (path to repo root, error message if any).
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
def get_remote_url(path: Path) -> Optional[str]:
|
|
111
|
+
try:
|
|
112
|
+
res = subprocess.run(
|
|
113
|
+
["git", "config", "--get", "remote.origin.url"],
|
|
114
|
+
cwd=path, capture_output=True, text=True
|
|
115
|
+
)
|
|
116
|
+
if res.returncode == 0:
|
|
117
|
+
return res.stdout.strip()
|
|
118
|
+
except Exception:
|
|
119
|
+
pass
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
# Case 1 & 2: Already in a git repo
|
|
123
|
+
remote = get_remote_url(current_cwd)
|
|
124
|
+
if remote:
|
|
125
|
+
# Simple check if owner/repo is in the remote URL
|
|
126
|
+
# Remotes can be git@github.com:owner/repo.git or https://github.com/owner/repo.git
|
|
127
|
+
if f"{owner}/{repo}" in remote or f"{owner}/{repo}.git" in remote:
|
|
128
|
+
return current_cwd, None
|
|
129
|
+
|
|
130
|
+
# Mismatch
|
|
131
|
+
if not quiet:
|
|
132
|
+
console.print(f"[yellow]Warning: Current directory is a git repo but remote '{remote}' does not match '{owner}/{repo}'. Proceeding in current directory.[/yellow]")
|
|
133
|
+
return current_cwd, None
|
|
134
|
+
|
|
135
|
+
# Case 3: Not in a git repo
|
|
136
|
+
target_dir = current_cwd / repo
|
|
137
|
+
|
|
138
|
+
# 3a: Subdirectory exists
|
|
139
|
+
if target_dir.exists() and target_dir.is_dir():
|
|
140
|
+
if (target_dir / ".git").exists():
|
|
141
|
+
if not quiet:
|
|
142
|
+
console.print(f"[blue]Found existing repository at {target_dir}[/blue]")
|
|
143
|
+
return target_dir, None
|
|
144
|
+
else:
|
|
145
|
+
return current_cwd, f"Directory '{repo}' exists but is not a git repository."
|
|
146
|
+
|
|
147
|
+
# 3b: Clone
|
|
148
|
+
if not quiet:
|
|
149
|
+
console.print(f"[blue]Cloning {owner}/{repo} into {target_dir}...[/blue]")
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
subprocess.run(
|
|
153
|
+
["gh", "repo", "clone", f"{owner}/{repo}"],
|
|
154
|
+
cwd=current_cwd,
|
|
155
|
+
check=True,
|
|
156
|
+
capture_output=True,
|
|
157
|
+
text=True
|
|
158
|
+
)
|
|
159
|
+
return target_dir, None
|
|
160
|
+
except subprocess.CalledProcessError as e:
|
|
161
|
+
err_msg = e.stderr if e.stderr else str(e)
|
|
162
|
+
return current_cwd, f"Failed to clone repository: {err_msg}"
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def run_agentic_architecture(
|
|
166
|
+
issue_url: str,
|
|
167
|
+
*,
|
|
168
|
+
verbose: bool = False,
|
|
169
|
+
quiet: bool = False,
|
|
170
|
+
timeout_adder: float = 0.0,
|
|
171
|
+
use_github_state: bool = True,
|
|
172
|
+
skip_prompts: bool = False
|
|
173
|
+
) -> Tuple[bool, str, float, str, List[str]]:
|
|
174
|
+
"""
|
|
175
|
+
Entry point for the agentic architecture workflow.
|
|
176
|
+
|
|
177
|
+
1. Validates the GitHub issue URL.
|
|
178
|
+
2. Fetches issue details and comments using `gh` CLI.
|
|
179
|
+
3. Ensures the repository is available locally (clones if necessary).
|
|
180
|
+
4. Invokes the architecture orchestrator.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
issue_url: Full URL to the GitHub issue.
|
|
184
|
+
verbose: Enable verbose logging.
|
|
185
|
+
quiet: Suppress non-error output.
|
|
186
|
+
timeout_adder: Additional seconds to add to step timeouts.
|
|
187
|
+
use_github_state: Whether to persist state to GitHub comments.
|
|
188
|
+
skip_prompts: If True, skip Step 9 (prompt generation). Default False (prompts ARE generated).
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Tuple containing:
|
|
192
|
+
- success (bool): Whether the workflow completed successfully.
|
|
193
|
+
- message (str): Final status message or error description.
|
|
194
|
+
- total_cost (float): Total estimated cost of LLM calls.
|
|
195
|
+
- model_used (str): The model used for the last step.
|
|
196
|
+
- output_files (List[str]): List of files generated/modified.
|
|
197
|
+
"""
|
|
198
|
+
cwd = Path.cwd()
|
|
199
|
+
|
|
200
|
+
# 1. Check gh CLI
|
|
201
|
+
if not _check_gh_cli():
|
|
202
|
+
return False, "gh CLI not found. Please install GitHub CLI.", 0.0, "", []
|
|
203
|
+
|
|
204
|
+
# 2. Parse URL
|
|
205
|
+
parsed = _parse_github_url(issue_url)
|
|
206
|
+
if not parsed:
|
|
207
|
+
return False, f"Invalid GitHub URL: {issue_url}", 0.0, "", []
|
|
208
|
+
|
|
209
|
+
owner, repo, issue_number = parsed
|
|
210
|
+
|
|
211
|
+
if not quiet:
|
|
212
|
+
console.print(f"[bold blue]Fetching issue #{issue_number} from {owner}/{repo}...[/bold blue]")
|
|
213
|
+
|
|
214
|
+
# 3. Fetch Issue Data
|
|
215
|
+
# gh api repos/{owner}/{repo}/issues/{number}
|
|
216
|
+
success, output = _run_gh_command(["api", f"repos/{owner}/{repo}/issues/{issue_number}"])
|
|
217
|
+
if not success:
|
|
218
|
+
return False, f"Issue not found: {output}", 0.0, "", []
|
|
219
|
+
|
|
220
|
+
try:
|
|
221
|
+
issue_data = json.loads(output)
|
|
222
|
+
except json.JSONDecodeError:
|
|
223
|
+
return False, "Failed to parse GitHub API response", 0.0, "", []
|
|
224
|
+
|
|
225
|
+
issue_title = issue_data.get("title", "")
|
|
226
|
+
issue_body = issue_data.get("body", "") or ""
|
|
227
|
+
issue_author = issue_data.get("user", {}).get("login", "unknown")
|
|
228
|
+
comments_url = issue_data.get("comments_url", "")
|
|
229
|
+
|
|
230
|
+
# 4. Fetch Comments
|
|
231
|
+
comments_text = ""
|
|
232
|
+
if comments_url:
|
|
233
|
+
c_success, c_output = _run_gh_command(["api", comments_url])
|
|
234
|
+
if c_success:
|
|
235
|
+
try:
|
|
236
|
+
comments = json.loads(c_output)
|
|
237
|
+
if isinstance(comments, list) and comments:
|
|
238
|
+
formatted_comments = []
|
|
239
|
+
for c in comments:
|
|
240
|
+
user = c.get("user", {}).get("login", "unknown")
|
|
241
|
+
body = c.get("body", "")
|
|
242
|
+
formatted_comments.append(f"User: {user}\n{body}")
|
|
243
|
+
comments_text = "\n\n--- Comments ---\n" + "\n\n".join(formatted_comments)
|
|
244
|
+
except json.JSONDecodeError:
|
|
245
|
+
if verbose:
|
|
246
|
+
console.print("[yellow]Warning: Failed to parse comments JSON[/yellow]")
|
|
247
|
+
|
|
248
|
+
full_issue_content = f"{issue_body}{comments_text}"
|
|
249
|
+
|
|
250
|
+
# 5. Ensure Repo Context
|
|
251
|
+
repo_path, error = _ensure_repo_context(owner, repo, cwd, quiet)
|
|
252
|
+
if error:
|
|
253
|
+
return False, error, 0.0, "", []
|
|
254
|
+
|
|
255
|
+
# 6. Invoke Orchestrator
|
|
256
|
+
return run_agentic_architecture_orchestrator(
|
|
257
|
+
issue_url=issue_url,
|
|
258
|
+
issue_content=full_issue_content,
|
|
259
|
+
repo_owner=owner,
|
|
260
|
+
repo_name=repo,
|
|
261
|
+
issue_number=issue_number,
|
|
262
|
+
issue_author=issue_author,
|
|
263
|
+
issue_title=issue_title,
|
|
264
|
+
cwd=repo_path,
|
|
265
|
+
verbose=verbose,
|
|
266
|
+
quiet=quiet,
|
|
267
|
+
timeout_adder=timeout_adder,
|
|
268
|
+
use_github_state=use_github_state,
|
|
269
|
+
skip_prompts=skip_prompts
|
|
270
|
+
)
|