pdd-cli 0.0.39__py3-none-any.whl → 0.0.41__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.

Potentially problematic release.


This version of pdd-cli might be problematic. Click here for more details.

pdd/__init__.py CHANGED
@@ -1,7 +1,10 @@
1
- __version__ = "0.0.39"
1
+ """PDD - Prompt Driven Development"""
2
+
3
+ __version__ = "0.0.41"
2
4
 
3
5
  # Strength parameter used for LLM extraction across the codebase
4
- # Used in postprocessing, XML tagging, code generation, and other extraction operations. The module should have a large context window and be affordable.
6
+ # Used in postprocessing, XML tagging, code generation, and other extraction
7
+ # operations. The module should have a large context window and be affordable.
5
8
  EXTRACTION_STRENGTH = 0.9
6
9
 
7
10
  DEFAULT_STRENGTH = 0.9
@@ -10,8 +13,6 @@ DEFAULT_TEMPERATURE = 0.0
10
13
 
11
14
  DEFAULT_TIME = 0.25
12
15
 
13
- """PDD - Prompt Driven Development"""
14
-
15
16
  # Define constants used across the package
16
17
  DEFAULT_LLM_MODEL = "gpt-4.1-nano"
17
18
  # When going to production, set the following constants:
pdd/auto_deps_main.py CHANGED
@@ -1,3 +1,4 @@
1
+ """Main function for the auto-deps command."""
1
2
  import sys
2
3
  from pathlib import Path
3
4
  from typing import Tuple, Optional
@@ -8,7 +9,7 @@ from . import DEFAULT_STRENGTH, DEFAULT_TIME
8
9
  from .construct_paths import construct_paths
9
10
  from .insert_includes import insert_includes
10
11
 
11
- def auto_deps_main(
12
+ def auto_deps_main( # pylint: disable=too-many-arguments, too-many-locals
12
13
  ctx: click.Context,
13
14
  prompt_file: str,
14
15
  directory_path: str,
@@ -57,7 +58,10 @@ def auto_deps_main(
57
58
  # Handle force_scan option
58
59
  if force_scan and Path(csv_path).exists():
59
60
  if not ctx.obj.get('quiet', False):
60
- rprint(f"[yellow]Removing existing CSV file due to --force-scan option: {csv_path}[/yellow]")
61
+ rprint(
62
+ "[yellow]Removing existing CSV file due to "
63
+ f"--force-scan option: {csv_path}[/yellow]"
64
+ )
61
65
  Path(csv_path).unlink()
62
66
 
63
67
  # Get strength and temperature from context
@@ -78,11 +82,11 @@ def auto_deps_main(
78
82
 
79
83
  # Save the modified prompt to the output file
80
84
  output_path = output_file_paths["output"]
81
- Path(output_path).write_text(modified_prompt)
85
+ Path(output_path).write_text(modified_prompt, encoding="utf-8")
82
86
 
83
87
  # Save the CSV output if it was generated
84
88
  if csv_output:
85
- Path(csv_path).write_text(csv_output)
89
+ Path(csv_path).write_text(csv_output, encoding="utf-8")
86
90
 
87
91
  # Provide user feedback
88
92
  if not ctx.obj.get('quiet', False):
@@ -94,7 +98,7 @@ def auto_deps_main(
94
98
 
95
99
  return modified_prompt, total_cost, model_name
96
100
 
97
- except Exception as e:
101
+ except Exception as exc:
98
102
  if not ctx.obj.get('quiet', False):
99
- rprint(f"[bold red]Error:[/bold red] {str(e)}")
103
+ rprint(f"[bold red]Error:[/bold red] {str(exc)}")
100
104
  sys.exit(1)
pdd/auto_include.py CHANGED
@@ -1,20 +1,113 @@
1
+ """
2
+ This module provides the `auto_include` function to automatically find and
3
+ insert dependencies into a prompt.
4
+ """
5
+ from io import StringIO
1
6
  from typing import Tuple, Optional
7
+
8
+ import pandas as pd
2
9
  from pydantic import BaseModel, Field
3
- from rich import print
4
10
  from rich.console import Console
5
11
  from rich.panel import Panel
6
- from .load_prompt_template import load_prompt_template
12
+
13
+ from . import DEFAULT_TIME, DEFAULT_STRENGTH
7
14
  from .llm_invoke import llm_invoke
15
+ from .load_prompt_template import load_prompt_template
8
16
  from .summarize_directory import summarize_directory
9
- import pandas as pd
10
- from io import StringIO
11
- from . import DEFAULT_TIME, DEFAULT_STRENGTH
12
17
 
13
18
  console = Console()
14
19
 
15
20
  class AutoIncludeOutput(BaseModel):
21
+ """
22
+ Pydantic model for the output of the auto_include extraction.
23
+ """
16
24
  string_of_includes: str = Field(description="The string of includes to be added to the prompt")
17
25
 
26
+
27
+ def _validate_input(input_prompt: str, directory_path: str, strength: float, temperature: float):
28
+ """Validate the inputs for the auto_include function."""
29
+ if not input_prompt:
30
+ raise ValueError("Input prompt cannot be empty")
31
+ if not directory_path:
32
+ raise ValueError("Invalid 'directory_path'.")
33
+ if not 0 <= strength <= 1:
34
+ raise ValueError("Strength must be between 0 and 1")
35
+ if not 0 <= temperature <= 1:
36
+ raise ValueError("Temperature must be between 0 and 1")
37
+
38
+
39
+ def _get_available_includes_from_csv(csv_output: str) -> list[str]:
40
+ """Parse the CSV output and return a list of available includes."""
41
+ if not csv_output:
42
+ return []
43
+ try:
44
+ # pylint: disable=invalid-name
45
+ dataframe = pd.read_csv(StringIO(csv_output))
46
+ return dataframe.apply(
47
+ lambda row: f"File: {row['full_path']}\nSummary: {row['file_summary']}",
48
+ axis=1
49
+ ).tolist()
50
+ except Exception as ex:
51
+ console.print(f"[red]Error parsing CSV: {str(ex)}[/red]")
52
+ return []
53
+
54
+
55
+ def _load_prompts() -> tuple[str, str]:
56
+ """Load the prompt templates."""
57
+ auto_include_prompt = load_prompt_template("auto_include_LLM")
58
+ extract_prompt = load_prompt_template("extract_auto_include_LLM")
59
+ if not auto_include_prompt or not extract_prompt:
60
+ raise ValueError("Failed to load prompt templates")
61
+ return auto_include_prompt, extract_prompt
62
+
63
+
64
+ def _summarize(directory_path: str, csv_file: Optional[str], llm_kwargs: dict) -> tuple[str, float, str]:
65
+ """Summarize the directory."""
66
+ return summarize_directory(
67
+ directory_path=directory_path,
68
+ csv_file=csv_file,
69
+ **llm_kwargs
70
+ )
71
+
72
+
73
+ def _run_llm_and_extract(
74
+ auto_include_prompt: str,
75
+ extract_prompt: str,
76
+ input_prompt: str,
77
+ available_includes: list[str],
78
+ llm_kwargs: dict,
79
+ ) -> tuple[str, float, str]:
80
+ """Run the LLM prompts and extract the dependencies."""
81
+ # pylint: disable=broad-except
82
+ # Run auto_include_LLM prompt
83
+ auto_include_response = llm_invoke(
84
+ prompt=auto_include_prompt,
85
+ input_json={
86
+ "input_prompt": input_prompt,
87
+ "available_includes": "\n".join(available_includes)
88
+ },
89
+ **llm_kwargs
90
+ )
91
+ total_cost = auto_include_response["cost"]
92
+ model_name = auto_include_response["model_name"]
93
+
94
+ # Run extract_auto_include_LLM prompt
95
+ try:
96
+ extract_response = llm_invoke(
97
+ prompt=extract_prompt,
98
+ input_json={"llm_output": auto_include_response["result"]},
99
+ output_pydantic=AutoIncludeOutput,
100
+ **llm_kwargs
101
+ )
102
+ total_cost += extract_response["cost"]
103
+ model_name = extract_response["model_name"]
104
+ dependencies = extract_response["result"].string_of_includes
105
+ except Exception as ex:
106
+ console.print(f"[red]Error extracting dependencies: {str(ex)}[/red]")
107
+ dependencies = ""
108
+ return dependencies, total_cost, model_name
109
+
110
+
18
111
  def auto_include(
19
112
  input_prompt: str,
20
113
  directory_path: str,
@@ -39,128 +132,77 @@ def auto_include(
39
132
  Returns:
40
133
  Tuple[str, str, float, str]: (dependencies, csv_output, total_cost, model_name)
41
134
  """
135
+ # pylint: disable=broad-except
42
136
  try:
43
- # Input validation
44
- if not input_prompt:
45
- raise ValueError("Input prompt cannot be empty")
46
- if not directory_path:
47
- raise ValueError("Invalid 'directory_path'.")
48
- if not 0 <= strength <= 1:
49
- raise ValueError("Strength must be between 0 and 1")
50
- if not 0 <= temperature <= 1:
51
- raise ValueError("Temperature must be between 0 and 1")
52
-
53
- total_cost = 0.0
54
- model_name = ""
137
+ _validate_input(input_prompt, directory_path, strength, temperature)
138
+
139
+ llm_kwargs = {
140
+ "strength": strength,
141
+ "temperature": temperature,
142
+ "time": time,
143
+ "verbose": verbose
144
+ }
55
145
 
56
146
  if verbose:
57
147
  console.print(Panel("Step 1: Loading prompt templates", style="blue"))
58
148
 
59
- # Load prompt templates
60
- auto_include_prompt = load_prompt_template("auto_include_LLM")
61
- extract_prompt = load_prompt_template("extract_auto_include_LLM")
62
-
63
- if not auto_include_prompt or not extract_prompt:
64
- raise ValueError("Failed to load prompt templates")
65
-
149
+ auto_include_prompt, extract_prompt = _load_prompts()
150
+
66
151
  if verbose:
67
152
  console.print(Panel("Step 2: Running summarize_directory", style="blue"))
68
153
 
69
- # Run summarize_directory
70
- csv_output, summary_cost, summary_model = summarize_directory(
71
- directory_path=directory_path,
72
- strength=strength,
73
- temperature=temperature,
74
- time=time,
75
- verbose=verbose,
76
- csv_file=csv_file
154
+ csv_output, summary_cost, summary_model = _summarize(
155
+ directory_path, csv_file, llm_kwargs
77
156
  )
78
- total_cost += summary_cost
79
- model_name = summary_model
80
-
81
- # Parse CSV to get available includes
82
- if not csv_output:
83
- available_includes = []
84
- else:
85
- try:
86
- df = pd.read_csv(StringIO(csv_output))
87
- available_includes = df.apply(
88
- lambda row: f"File: {row['full_path']}\nSummary: {row['file_summary']}",
89
- axis=1
90
- ).tolist()
91
- except Exception as e:
92
- console.print(f"[red]Error parsing CSV: {str(e)}[/red]")
93
- available_includes = []
94
157
 
158
+ available_includes = _get_available_includes_from_csv(csv_output)
159
+
95
160
  if verbose:
96
161
  console.print(Panel("Step 3: Running auto_include_LLM prompt", style="blue"))
97
162
 
98
- # Run auto_include_LLM prompt
99
- auto_include_response = llm_invoke(
100
- prompt=auto_include_prompt,
101
- input_json={
102
- "input_prompt": input_prompt,
103
- "available_includes": "\n".join(available_includes)
104
- },
105
- strength=strength,
106
- temperature=temperature,
107
- time=time,
108
- verbose=verbose
163
+ dependencies, llm_cost, llm_model_name = _run_llm_and_extract(
164
+ auto_include_prompt=auto_include_prompt,
165
+ extract_prompt=extract_prompt,
166
+ input_prompt=input_prompt,
167
+ available_includes=available_includes,
168
+ llm_kwargs=llm_kwargs,
109
169
  )
110
- total_cost += auto_include_response["cost"]
111
- model_name = auto_include_response["model_name"]
170
+
171
+ total_cost = summary_cost + llm_cost
172
+ model_name = llm_model_name or summary_model
112
173
 
113
174
  if verbose:
114
- console.print(Panel("Step 4: Running extract_auto_include_LLM prompt", style="blue"))
115
-
116
- # Run extract_auto_include_LLM prompt
117
- try:
118
- extract_response = llm_invoke(
119
- prompt=extract_prompt,
120
- input_json={"llm_output": auto_include_response["result"]},
121
- strength=strength,
122
- temperature=temperature,
123
- time=time,
124
- verbose=verbose,
125
- output_pydantic=AutoIncludeOutput
126
- )
127
- total_cost += extract_response["cost"]
128
- model_name = extract_response["model_name"]
129
-
130
- if verbose:
131
- console.print(Panel("Step 5: Extracting dependencies", style="blue"))
132
-
133
- # Extract dependencies
134
- dependencies = extract_response["result"].string_of_includes
135
- except Exception as e:
136
- console.print(f"[red]Error extracting dependencies: {str(e)}[/red]")
137
- dependencies = ""
138
-
139
- if verbose:
140
- console.print(Panel(f"""
141
- Results:
142
- Dependencies: {dependencies}
143
- CSV Output: {csv_output}
144
- Total Cost: ${total_cost:.6f}
145
- Model Used: {model_name}
146
- """, style="green"))
175
+ console.print(Panel(
176
+ (
177
+ f"Results:\n"
178
+ f"Dependencies: {dependencies}\n"
179
+ f"CSV Output: {csv_output}\n"
180
+ f"Total Cost: ${total_cost:.6f}\n"
181
+ f"Model Used: {model_name}"
182
+ ),
183
+ style="green"
184
+ ))
147
185
 
148
186
  return dependencies, csv_output, total_cost, model_name
149
187
 
150
- except Exception as e:
151
- console.print(f"[red]Error in auto_include: {str(e)}[/red]")
188
+ except Exception as ex:
189
+ console.print(f"[red]Error in auto_include: {str(ex)}[/red]")
152
190
  raise
153
191
 
192
+
154
193
  def main():
155
194
  """Example usage of auto_include function"""
156
195
  try:
157
196
  # Example inputs
158
197
  input_prompt = "Write a function to process image data"
159
198
  directory_path = "context/c*.py"
160
- csv_file = """full_path,file_summary,date
161
- context/image_utils.py,"Image processing utilities",2023-01-01T10:00:00"""
199
+ csv_file = (
200
+ "full_path,file_summary,date\n"
201
+ "context/image_utils.py,"
202
+ "\"Image processing utilities\",2023-01-01T10:00:00"
203
+ )
162
204
 
163
- dependencies, csv_output, total_cost, model_name = auto_include(
205
+ dependencies, _, total_cost, model_name = auto_include(
164
206
  input_prompt=input_prompt,
165
207
  directory_path=directory_path,
166
208
  csv_file=csv_file,
@@ -175,8 +217,8 @@ context/image_utils.py,"Image processing utilities",2023-01-01T10:00:00"""
175
217
  console.print(f"Total Cost: ${total_cost:.6f}")
176
218
  console.print(f"Model Used: {model_name}")
177
219
 
178
- except Exception as e:
179
- console.print(f"[red]Error in main: {str(e)}[/red]")
220
+ except Exception as ex:
221
+ console.print(f"[red]Error in main: {str(ex)}[/red]")
180
222
 
181
223
  if __name__ == "__main__":
182
224
  main()
pdd/auto_update.py CHANGED
@@ -1,9 +1,12 @@
1
+ """This module provides a function to automatically update the package."""
1
2
  import importlib.metadata
2
- import requests
3
- import semver
3
+ import shutil
4
4
  import subprocess
5
5
  import sys
6
- import shutil
6
+ from typing import Optional
7
+ import requests
8
+ import semver
9
+
7
10
 
8
11
  def detect_installation_method(sys_executable):
9
12
  """
@@ -38,12 +41,59 @@ def get_upgrade_command(package_name, installation_method):
38
41
  uv_path = shutil.which("uv")
39
42
  if uv_path:
40
43
  return ([uv_path, "tool", "install", package_name, "--force"], False)
41
- else:
42
- # If uv isn't in PATH, use shell=True
43
- return (["uv", "tool", "install", package_name, "--force"], True)
44
- else:
45
- # Default pip method
46
- return ([sys.executable, "-m", "pip", "install", "--upgrade", package_name], False)
44
+ # If uv isn't in PATH, use shell=True
45
+ return (["uv", "tool", "install", package_name, "--force"], True)
46
+ # Default pip method
47
+ return ([sys.executable, "-m", "pip", "install", "--upgrade", package_name], False)
48
+
49
+
50
+ def _get_latest_version(package_name: str) -> Optional[str]:
51
+ """Fetch the latest version of a package from PyPI."""
52
+ # pylint: disable=broad-except
53
+ try:
54
+ pypi_url = f"https://pypi.org/pypi/{package_name}/json"
55
+ response = requests.get(pypi_url, timeout=10)
56
+ response.raise_for_status()
57
+ return response.json()['info']['version']
58
+ except Exception as ex:
59
+ print(f"Failed to fetch latest version from PyPI: {str(ex)}")
60
+ return None
61
+
62
+
63
+ def _upgrade_package(package_name: str, installation_method: str):
64
+ """Upgrade a package using the specified installation method."""
65
+ cmd, use_shell = get_upgrade_command(package_name, installation_method)
66
+ cmd_str = " ".join(cmd)
67
+ print(f"\nDetected installation method: {installation_method}")
68
+ print(f"Upgrading with command: {cmd_str}")
69
+
70
+ # pylint: disable=broad-except
71
+ try:
72
+ result = subprocess.run(
73
+ cmd,
74
+ shell=use_shell,
75
+ capture_output=True,
76
+ text=True,
77
+ check=False
78
+ )
79
+ if result.returncode == 0:
80
+ print(f"\nSuccessfully upgraded {package_name}")
81
+ return True
82
+ print(f"\nUpgrade command failed: {result.stderr}")
83
+ return False
84
+ except Exception as ex:
85
+ print(f"\nError during upgrade: {str(ex)}")
86
+ return False
87
+
88
+
89
+ def _is_new_version_available(current_version: str, latest_version: str) -> bool:
90
+ """Check if a new version is available."""
91
+ try:
92
+ current_semver = semver.VersionInfo.parse(current_version)
93
+ latest_semver = semver.VersionInfo.parse(latest_version)
94
+ return latest_semver > current_semver
95
+ except ValueError:
96
+ return latest_version != current_version
47
97
 
48
98
 
49
99
  def auto_update(package_name: str = "pdd-cli", latest_version: str = None) -> None:
@@ -55,85 +105,43 @@ def auto_update(package_name: str = "pdd-cli", latest_version: str = None) -> No
55
105
  latest_version (str): Known latest version (default: None)
56
106
  package_name (str): Name of the package to check (default: "pdd-cli")
57
107
  """
108
+ # pylint: disable=broad-except
58
109
  try:
59
- # Get current installed version
60
110
  current_version = importlib.metadata.version(package_name)
61
111
 
62
- # If latest_version is not provided, fetch from PyPI
63
112
  if latest_version is None:
64
- try:
65
- pypi_url = f"https://pypi.org/pypi/{package_name}/json"
66
- response = requests.get(pypi_url)
67
- response.raise_for_status()
68
- latest_version = response.json()['info']['version']
69
- except Exception as e:
70
- print(f"Failed to fetch latest version from PyPI: {str(e)}")
113
+ latest_version = _get_latest_version(package_name)
114
+ if latest_version is None:
71
115
  return
72
116
 
73
- # Compare versions using semantic versioning
74
- try:
75
- current_semver = semver.VersionInfo.parse(current_version)
76
- latest_semver = semver.VersionInfo.parse(latest_version)
77
- except ValueError:
78
- # If versions don't follow semantic versioning, fall back to string comparison
79
- if current_version == latest_version:
80
- return
81
- else:
82
- # If versions follow semantic versioning, compare properly
83
- if current_semver >= latest_semver:
84
- return
117
+ if not _is_new_version_available(current_version, latest_version):
118
+ return
85
119
 
86
- # If we get here, there's a new version available
87
- print(f"\nNew version of {package_name} available: {latest_version} (current: {current_version})")
120
+ print(f"\nNew version of {package_name} available: "
121
+ f"{latest_version} (current: {current_version})")
88
122
 
89
- # Ask for user confirmation
90
123
  while True:
91
124
  response = input("Would you like to upgrade? [y/N]: ").lower().strip()
92
125
  if response in ['y', 'yes']:
93
- # Detect installation method
94
126
  installation_method = detect_installation_method(sys.executable)
95
- cmd, use_shell = get_upgrade_command(package_name, installation_method)
127
+ if _upgrade_package(package_name, installation_method):
128
+ break
96
129
 
97
- cmd_str = " ".join(cmd)
98
- print(f"\nDetected installation method: {installation_method}")
99
- print(f"Upgrading with command: {cmd_str}")
130
+ if installation_method == "uv":
131
+ print("\nAttempting fallback to pip...")
132
+ if _upgrade_package(package_name, "pip"):
133
+ break
100
134
 
101
- try:
102
- result = subprocess.run(cmd, shell=use_shell, capture_output=True, text=True)
103
-
104
- if result.returncode == 0:
105
- print(f"\nSuccessfully upgraded {package_name} to version {latest_version}")
106
- else:
107
- print(f"\nUpgrade command failed: {result.stderr}")
108
-
109
- # If UV failed and we're not already in fallback mode, try pip as fallback
110
- if installation_method == "uv":
111
- print("\nAttempting fallback to pip...")
112
- fallback_cmd, fallback_shell = get_upgrade_command(package_name, "pip")
113
- fallback_str = " ".join(fallback_cmd)
114
- print(f"Fallback command: {fallback_str}")
115
-
116
- try:
117
- fallback_result = subprocess.run(fallback_cmd, shell=fallback_shell, capture_output=True, text=True)
118
- if fallback_result.returncode == 0:
119
- print(f"\nSuccessfully upgraded {package_name} using fallback method")
120
- else:
121
- print(f"\nFallback upgrade failed: {fallback_result.stderr}")
122
- except Exception as fallback_err:
123
- print(f"\nError during fallback upgrade: {str(fallback_err)}")
124
- except Exception as e:
125
- print(f"\nError during upgrade: {str(e)}")
126
135
  break
127
- elif response in ['n', 'no', '']:
136
+ if response in ['n', 'no', '']:
128
137
  print("\nUpgrade cancelled")
129
138
  break
130
- else:
131
- print("Please answer 'y' or 'n'")
139
+ print("Please answer 'y' or 'n'")
132
140
 
133
141
  except importlib.metadata.PackageNotFoundError:
134
142
  print(f"Package {package_name} is not installed")
135
- except Exception as e:
136
- print(f"Error checking for updates: {str(e)}")
143
+ except Exception as ex:
144
+ print(f"Error checking for updates: {str(ex)}")
137
145
 
138
146
 
139
147
  if __name__ == "__main__":
pdd/bug_main.py CHANGED
@@ -76,8 +76,8 @@ def bug_main(
76
76
  program_content,
77
77
  strength,
78
78
  temperature,
79
- language,
80
- time_budget
79
+ time_budget,
80
+ language
81
81
  )
82
82
 
83
83
  # Save results if output path is provided