pdd-cli 0.0.45__py3-none-any.whl → 0.0.90__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 +4 -4
- pdd/agentic_common.py +863 -0
- pdd/agentic_crash.py +534 -0
- pdd/agentic_fix.py +1179 -0
- pdd/agentic_langtest.py +162 -0
- pdd/agentic_update.py +370 -0
- pdd/agentic_verify.py +183 -0
- pdd/auto_deps_main.py +15 -5
- pdd/auto_include.py +63 -5
- pdd/bug_main.py +3 -2
- pdd/bug_to_unit_test.py +2 -0
- pdd/change_main.py +11 -4
- pdd/cli.py +22 -1181
- pdd/cmd_test_main.py +73 -21
- pdd/code_generator.py +58 -18
- pdd/code_generator_main.py +672 -25
- pdd/commands/__init__.py +42 -0
- pdd/commands/analysis.py +248 -0
- pdd/commands/fix.py +140 -0
- pdd/commands/generate.py +257 -0
- pdd/commands/maintenance.py +174 -0
- pdd/commands/misc.py +79 -0
- pdd/commands/modify.py +230 -0
- pdd/commands/report.py +144 -0
- pdd/commands/templates.py +215 -0
- pdd/commands/utility.py +110 -0
- pdd/config_resolution.py +58 -0
- pdd/conflicts_main.py +8 -3
- pdd/construct_paths.py +258 -82
- pdd/context_generator.py +10 -2
- pdd/context_generator_main.py +113 -11
- pdd/continue_generation.py +47 -7
- pdd/core/__init__.py +0 -0
- pdd/core/cli.py +503 -0
- pdd/core/dump.py +554 -0
- pdd/core/errors.py +63 -0
- pdd/core/utils.py +90 -0
- pdd/crash_main.py +44 -11
- pdd/data/language_format.csv +71 -63
- pdd/data/llm_model.csv +20 -18
- pdd/detect_change_main.py +5 -4
- pdd/fix_code_loop.py +330 -76
- pdd/fix_error_loop.py +207 -61
- pdd/fix_errors_from_unit_tests.py +4 -3
- pdd/fix_main.py +75 -18
- pdd/fix_verification_errors.py +12 -100
- pdd/fix_verification_errors_loop.py +306 -272
- pdd/fix_verification_main.py +28 -9
- pdd/generate_output_paths.py +93 -10
- pdd/generate_test.py +16 -5
- pdd/get_jwt_token.py +9 -2
- pdd/get_run_command.py +73 -0
- pdd/get_test_command.py +68 -0
- pdd/git_update.py +70 -19
- pdd/incremental_code_generator.py +2 -2
- pdd/insert_includes.py +11 -3
- pdd/llm_invoke.py +1269 -103
- pdd/load_prompt_template.py +36 -10
- pdd/pdd_completion.fish +25 -2
- pdd/pdd_completion.sh +30 -4
- pdd/pdd_completion.zsh +79 -4
- pdd/postprocess.py +10 -3
- pdd/preprocess.py +228 -15
- pdd/preprocess_main.py +8 -5
- pdd/prompts/agentic_crash_explore_LLM.prompt +49 -0
- pdd/prompts/agentic_fix_explore_LLM.prompt +45 -0
- pdd/prompts/agentic_fix_harvest_only_LLM.prompt +48 -0
- pdd/prompts/agentic_fix_primary_LLM.prompt +85 -0
- pdd/prompts/agentic_update_LLM.prompt +1071 -0
- pdd/prompts/agentic_verify_explore_LLM.prompt +45 -0
- pdd/prompts/auto_include_LLM.prompt +100 -905
- pdd/prompts/detect_change_LLM.prompt +122 -20
- pdd/prompts/example_generator_LLM.prompt +22 -1
- pdd/prompts/extract_code_LLM.prompt +5 -1
- pdd/prompts/extract_program_code_fix_LLM.prompt +7 -1
- pdd/prompts/extract_prompt_update_LLM.prompt +7 -8
- pdd/prompts/extract_promptline_LLM.prompt +17 -11
- pdd/prompts/find_verification_errors_LLM.prompt +6 -0
- pdd/prompts/fix_code_module_errors_LLM.prompt +4 -2
- pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +8 -0
- pdd/prompts/fix_verification_errors_LLM.prompt +22 -0
- pdd/prompts/generate_test_LLM.prompt +21 -6
- pdd/prompts/increase_tests_LLM.prompt +1 -5
- pdd/prompts/insert_includes_LLM.prompt +228 -108
- pdd/prompts/trace_LLM.prompt +25 -22
- pdd/prompts/unfinished_prompt_LLM.prompt +85 -1
- pdd/prompts/update_prompt_LLM.prompt +22 -1
- pdd/pytest_output.py +127 -12
- pdd/render_mermaid.py +236 -0
- pdd/setup_tool.py +648 -0
- pdd/simple_math.py +2 -0
- pdd/split_main.py +3 -2
- pdd/summarize_directory.py +49 -6
- pdd/sync_determine_operation.py +543 -98
- pdd/sync_main.py +81 -31
- pdd/sync_orchestration.py +1334 -751
- pdd/sync_tui.py +848 -0
- pdd/template_registry.py +264 -0
- pdd/templates/architecture/architecture_json.prompt +242 -0
- pdd/templates/generic/generate_prompt.prompt +174 -0
- pdd/trace.py +168 -12
- pdd/trace_main.py +4 -3
- pdd/track_cost.py +151 -61
- pdd/unfinished_prompt.py +49 -3
- pdd/update_main.py +549 -67
- pdd/update_model_costs.py +2 -2
- pdd/update_prompt.py +19 -4
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.90.dist-info}/METADATA +19 -6
- pdd_cli-0.0.90.dist-info/RECORD +153 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.90.dist-info}/licenses/LICENSE +1 -1
- pdd_cli-0.0.45.dist-info/RECORD +0 -116
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.90.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.90.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.90.dist-info}/top_level.txt +0 -0
pdd/fix_verification_main.py
CHANGED
|
@@ -3,6 +3,7 @@ import os
|
|
|
3
3
|
import subprocess
|
|
4
4
|
import click
|
|
5
5
|
import logging
|
|
6
|
+
from pathlib import Path
|
|
6
7
|
from typing import Optional, Tuple, List, Dict, Any
|
|
7
8
|
|
|
8
9
|
# Use Rich for pretty printing to the console
|
|
@@ -116,6 +117,9 @@ def fix_verification_main(
|
|
|
116
117
|
verification_program: Optional[str], # Only used if loop=True
|
|
117
118
|
max_attempts: int = DEFAULT_MAX_ATTEMPTS,
|
|
118
119
|
budget: float = DEFAULT_BUDGET,
|
|
120
|
+
agentic_fallback: bool = True,
|
|
121
|
+
strength: Optional[float] = None,
|
|
122
|
+
temperature: Optional[float] = None,
|
|
119
123
|
) -> Tuple[bool, str, str, int, float, str]:
|
|
120
124
|
"""
|
|
121
125
|
CLI wrapper for the 'verify' command. Verifies code correctness by running
|
|
@@ -144,9 +148,9 @@ def fix_verification_main(
|
|
|
144
148
|
- total_cost (float): Total cost incurred.
|
|
145
149
|
- model_name (str): Name of the LLM used.
|
|
146
150
|
"""
|
|
147
|
-
# Extract global options from context
|
|
148
|
-
strength: float = ctx.obj.get('strength', DEFAULT_STRENGTH)
|
|
149
|
-
temperature: float = ctx.obj.get('temperature', DEFAULT_TEMPERATURE)
|
|
151
|
+
# Extract global options from context (prefer passed parameters over ctx.obj)
|
|
152
|
+
strength: float = strength if strength is not None else ctx.obj.get('strength', DEFAULT_STRENGTH)
|
|
153
|
+
temperature: float = temperature if temperature is not None else ctx.obj.get('temperature', DEFAULT_TEMPERATURE)
|
|
150
154
|
force: bool = ctx.obj.get('force', False)
|
|
151
155
|
quiet: bool = ctx.obj.get('quiet', False)
|
|
152
156
|
verbose: bool = ctx.obj.get('verbose', False)
|
|
@@ -204,6 +208,8 @@ def fix_verification_main(
|
|
|
204
208
|
quiet=quiet,
|
|
205
209
|
command="verify",
|
|
206
210
|
command_options=command_options,
|
|
211
|
+
context_override=ctx.obj.get('context'),
|
|
212
|
+
confirm_callback=ctx.obj.get('confirm_callback')
|
|
207
213
|
)
|
|
208
214
|
output_code_path = output_file_paths.get("output_code")
|
|
209
215
|
output_results_path = output_file_paths.get("output_results")
|
|
@@ -212,6 +218,9 @@ def fix_verification_main(
|
|
|
212
218
|
if verbose:
|
|
213
219
|
rich_print("[dim]Resolved output paths via construct_paths.[/dim]")
|
|
214
220
|
|
|
221
|
+
except click.Abort:
|
|
222
|
+
# User cancelled - re-raise to stop the sync loop
|
|
223
|
+
raise
|
|
215
224
|
except Exception as e:
|
|
216
225
|
# If the helper does not understand the "verify" command fall back.
|
|
217
226
|
if "invalid command" in str(e).lower():
|
|
@@ -230,7 +239,8 @@ def fix_verification_main(
|
|
|
230
239
|
input_strings["program_file"] = f.read()
|
|
231
240
|
except FileNotFoundError as fe:
|
|
232
241
|
rich_print(f"[bold red]Error:[/bold red] {fe}")
|
|
233
|
-
sys.exit(1)
|
|
242
|
+
# Return error result instead of sys.exit(1) to allow orchestrator to handle gracefully
|
|
243
|
+
return False, "", "", 0, 0.0, f"FileNotFoundError: {fe}"
|
|
234
244
|
|
|
235
245
|
# Pick or build output paths
|
|
236
246
|
if output_code_path is None:
|
|
@@ -252,7 +262,8 @@ def fix_verification_main(
|
|
|
252
262
|
if verbose:
|
|
253
263
|
import traceback
|
|
254
264
|
rich_print(Panel(traceback.format_exc(), title="Traceback", border_style="red"))
|
|
255
|
-
sys.exit(1)
|
|
265
|
+
# Return error result instead of sys.exit(1) to allow orchestrator to handle gracefully
|
|
266
|
+
return False, "", "", 0, 0.0, f"Error: {e}"
|
|
256
267
|
|
|
257
268
|
# --- Core Logic ---
|
|
258
269
|
success: bool = False
|
|
@@ -273,6 +284,7 @@ def fix_verification_main(
|
|
|
273
284
|
program_file=program_file, # Changed to pass the program_file path
|
|
274
285
|
code_file=code_file, # Changed to pass the code_file path
|
|
275
286
|
prompt=input_strings["prompt_file"], # Correctly passing prompt content
|
|
287
|
+
prompt_file=prompt_file,
|
|
276
288
|
verification_program=verification_program, # Path to the verifier program
|
|
277
289
|
strength=strength,
|
|
278
290
|
temperature=temperature,
|
|
@@ -283,7 +295,8 @@ def fix_verification_main(
|
|
|
283
295
|
# output_code_path should not be passed here
|
|
284
296
|
# output_program_path should not be passed here
|
|
285
297
|
verbose=verbose,
|
|
286
|
-
program_args=[] # Pass an empty list for program_args
|
|
298
|
+
program_args=[], # Pass an empty list for program_args
|
|
299
|
+
agentic_fallback=agentic_fallback,
|
|
287
300
|
)
|
|
288
301
|
success = loop_results.get('success', False)
|
|
289
302
|
final_program = loop_results.get('final_program', "") # Use .get for safety
|
|
@@ -407,7 +420,9 @@ def fix_verification_main(
|
|
|
407
420
|
try:
|
|
408
421
|
if verbose:
|
|
409
422
|
rich_print(f"[cyan bold DEBUG] In fix_verification_main, ATTEMPTING to write code to: {output_code_path!r}")
|
|
410
|
-
|
|
423
|
+
output_code_path_obj = Path(output_code_path)
|
|
424
|
+
output_code_path_obj.parent.mkdir(parents=True, exist_ok=True)
|
|
425
|
+
with open(output_code_path_obj, "w") as f:
|
|
411
426
|
f.write(final_code)
|
|
412
427
|
saved_code_path = output_code_path
|
|
413
428
|
if not quiet:
|
|
@@ -427,7 +442,9 @@ def fix_verification_main(
|
|
|
427
442
|
try:
|
|
428
443
|
if verbose:
|
|
429
444
|
rich_print(f"[cyan bold DEBUG] In fix_verification_main, ATTEMPTING to write program to: {output_program_path!r}")
|
|
430
|
-
|
|
445
|
+
output_program_path_obj = Path(output_program_path)
|
|
446
|
+
output_program_path_obj.parent.mkdir(parents=True, exist_ok=True)
|
|
447
|
+
with open(output_program_path_obj, "w") as f:
|
|
431
448
|
f.write(final_program)
|
|
432
449
|
saved_program_path = output_program_path
|
|
433
450
|
if not quiet:
|
|
@@ -437,7 +454,9 @@ def fix_verification_main(
|
|
|
437
454
|
|
|
438
455
|
if not loop and output_results_path:
|
|
439
456
|
try:
|
|
440
|
-
|
|
457
|
+
output_results_path_obj = Path(output_results_path)
|
|
458
|
+
output_results_path_obj.parent.mkdir(parents=True, exist_ok=True)
|
|
459
|
+
with open(output_results_path_obj, "w") as f:
|
|
441
460
|
f.write(results_log_content)
|
|
442
461
|
saved_results_path = output_results_path
|
|
443
462
|
if not quiet:
|
pdd/generate_output_paths.py
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import logging
|
|
3
|
-
from typing import Dict, List, Optional
|
|
3
|
+
from typing import Dict, List, Literal, Optional
|
|
4
|
+
|
|
5
|
+
# Type alias for path resolution mode
|
|
6
|
+
PathResolutionMode = Literal["config_base", "cwd"]
|
|
4
7
|
|
|
5
8
|
# Configure logging
|
|
6
|
-
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
7
9
|
logger = logging.getLogger(__name__)
|
|
8
10
|
|
|
9
11
|
# --- Configuration Data ---
|
|
10
12
|
|
|
13
|
+
# Default directory names
|
|
14
|
+
EXAMPLES_DIR = "examples"
|
|
15
|
+
|
|
11
16
|
# Define the expected output keys for each command
|
|
12
17
|
# Use underscores for keys as requested
|
|
13
18
|
COMMAND_OUTPUT_KEYS: Dict[str, List[str]] = {
|
|
@@ -178,14 +183,19 @@ def generate_output_paths(
|
|
|
178
183
|
basename: str,
|
|
179
184
|
language: str,
|
|
180
185
|
file_extension: str,
|
|
181
|
-
context_config: Optional[Dict[str, str]] = None
|
|
186
|
+
context_config: Optional[Dict[str, str]] = None,
|
|
187
|
+
input_file_dir: Optional[str] = None,
|
|
188
|
+
input_file_dirs: Optional[Dict[str, str]] = None,
|
|
189
|
+
config_base_dir: Optional[str] = None,
|
|
190
|
+
path_resolution_mode: PathResolutionMode = "config_base",
|
|
182
191
|
) -> Dict[str, str]:
|
|
183
192
|
"""
|
|
184
193
|
Generates the full, absolute output paths for a given PDD command.
|
|
185
194
|
|
|
186
|
-
It prioritizes user-specified paths (--output options), then context
|
|
187
|
-
configuration from .pddrc, then environment variables, and finally
|
|
188
|
-
falls back to default naming conventions in the
|
|
195
|
+
It prioritizes user-specified paths (--output options), then context
|
|
196
|
+
configuration from .pddrc, then environment variables, and finally
|
|
197
|
+
falls back to default naming conventions in the input file's directory
|
|
198
|
+
(or current working directory if input_file_dir is not provided).
|
|
189
199
|
|
|
190
200
|
Args:
|
|
191
201
|
command: The PDD command being executed (e.g., 'generate', 'fix').
|
|
@@ -199,6 +209,23 @@ def generate_output_paths(
|
|
|
199
209
|
used when default patterns require it.
|
|
200
210
|
context_config: Optional dictionary with context-specific paths from .pddrc
|
|
201
211
|
configuration (e.g., {'generate_output_path': 'src/'}).
|
|
212
|
+
input_file_dir: Optional path to the input file's directory. When provided,
|
|
213
|
+
default output files will be placed in this directory instead
|
|
214
|
+
of the current working directory.
|
|
215
|
+
input_file_dirs: Optional dictionary mapping output keys to specific input
|
|
216
|
+
file directories. When provided, each output will use its
|
|
217
|
+
corresponding input file directory (e.g., {'output_code': 'src/main/java'}).
|
|
218
|
+
config_base_dir: Optional base directory to resolve relative `.pddrc` and
|
|
219
|
+
environment variable output paths. When set, relative
|
|
220
|
+
config paths resolve under this directory (typically the
|
|
221
|
+
directory containing `.pddrc`) instead of the input file
|
|
222
|
+
directory.
|
|
223
|
+
path_resolution_mode: Controls how relative paths from `.pddrc` and
|
|
224
|
+
environment variables are resolved. "config_base"
|
|
225
|
+
(default) resolves relative to config_base_dir,
|
|
226
|
+
"cwd" resolves relative to the current working
|
|
227
|
+
directory. Use "cwd" for sync command to ensure
|
|
228
|
+
output files are created where the user is.
|
|
202
229
|
|
|
203
230
|
Returns:
|
|
204
231
|
A dictionary where keys are the standardized output identifiers
|
|
@@ -209,9 +236,14 @@ def generate_output_paths(
|
|
|
209
236
|
logger.debug(f"Generating output paths for command: {command}")
|
|
210
237
|
logger.debug(f"User output locations: {output_locations}")
|
|
211
238
|
logger.debug(f"Context config: {context_config}")
|
|
239
|
+
logger.debug(f"Input file dirs: {input_file_dirs}")
|
|
240
|
+
logger.debug(f"Config base dir: {config_base_dir}")
|
|
241
|
+
logger.debug(f"Path resolution mode: {path_resolution_mode}")
|
|
212
242
|
logger.debug(f"Basename: {basename}, Language: {language}, Extension: {file_extension}")
|
|
213
243
|
|
|
214
244
|
context_config = context_config or {}
|
|
245
|
+
input_file_dirs = input_file_dirs or {}
|
|
246
|
+
config_base_dir_abs = os.path.abspath(config_base_dir) if config_base_dir else None
|
|
215
247
|
result_paths: Dict[str, str] = {}
|
|
216
248
|
|
|
217
249
|
if not basename:
|
|
@@ -278,6 +310,20 @@ def generate_output_paths(
|
|
|
278
310
|
# 2. Check Context Configuration Path (.pddrc)
|
|
279
311
|
elif context_path:
|
|
280
312
|
source = "context"
|
|
313
|
+
|
|
314
|
+
# Resolve relative `.pddrc` paths based on path_resolution_mode.
|
|
315
|
+
# "cwd" mode: resolve relative to current working directory (for sync)
|
|
316
|
+
# "config_base" mode: resolve relative to config_base_dir (for fix, etc.)
|
|
317
|
+
# Fall back to the input file directory for backwards compatibility.
|
|
318
|
+
if not os.path.isabs(context_path):
|
|
319
|
+
if path_resolution_mode == "cwd":
|
|
320
|
+
context_path = os.path.join(os.getcwd(), context_path)
|
|
321
|
+
elif config_base_dir_abs:
|
|
322
|
+
context_path = os.path.join(config_base_dir_abs, context_path)
|
|
323
|
+
elif input_file_dir:
|
|
324
|
+
context_path = os.path.join(input_file_dir, context_path)
|
|
325
|
+
logger.debug(f"Resolved relative context path to: {context_path}")
|
|
326
|
+
|
|
281
327
|
# Check if the context path is a directory
|
|
282
328
|
is_dir = context_path.endswith(os.path.sep) or context_path.endswith('/')
|
|
283
329
|
if not is_dir:
|
|
@@ -297,6 +343,18 @@ def generate_output_paths(
|
|
|
297
343
|
# 3. Check Environment Variable Path
|
|
298
344
|
elif env_path:
|
|
299
345
|
source = "environment"
|
|
346
|
+
|
|
347
|
+
# Resolve relative env paths based on path_resolution_mode.
|
|
348
|
+
# Same logic as .pddrc paths for consistency.
|
|
349
|
+
if not os.path.isabs(env_path):
|
|
350
|
+
if path_resolution_mode == "cwd":
|
|
351
|
+
env_path = os.path.join(os.getcwd(), env_path)
|
|
352
|
+
elif config_base_dir_abs:
|
|
353
|
+
env_path = os.path.join(config_base_dir_abs, env_path)
|
|
354
|
+
elif input_file_dir:
|
|
355
|
+
env_path = os.path.join(input_file_dir, env_path)
|
|
356
|
+
logger.debug(f"Resolved relative env path to: {env_path}")
|
|
357
|
+
|
|
300
358
|
# Check if the environment variable points to a directory
|
|
301
359
|
is_dir = env_path.endswith(os.path.sep)
|
|
302
360
|
if not is_dir:
|
|
@@ -313,11 +371,33 @@ def generate_output_paths(
|
|
|
313
371
|
logger.debug(f"Env path '{env_path}' identified as a specific file path.")
|
|
314
372
|
final_path = env_path # Assume it's a full path or filename
|
|
315
373
|
|
|
316
|
-
# 4. Use Default Naming Convention
|
|
374
|
+
# 4. Use Default Naming Convention
|
|
317
375
|
else:
|
|
318
376
|
source = "default"
|
|
319
|
-
|
|
320
|
-
|
|
377
|
+
# For example command, default to examples/ directory if no .pddrc config
|
|
378
|
+
if command == "example":
|
|
379
|
+
examples_dir = EXAMPLES_DIR # Fallback constant
|
|
380
|
+
# Create examples directory if it doesn't exist
|
|
381
|
+
try:
|
|
382
|
+
os.makedirs(examples_dir, exist_ok=True)
|
|
383
|
+
logger.debug(f"Created examples directory: {examples_dir}")
|
|
384
|
+
except OSError as e:
|
|
385
|
+
logger.warning(f"Could not create examples directory: {e}")
|
|
386
|
+
final_path = os.path.join(examples_dir, default_filename)
|
|
387
|
+
logger.debug(f"Using default filename '{default_filename}' in examples directory.")
|
|
388
|
+
else:
|
|
389
|
+
# First check if there's a specific directory for this output key
|
|
390
|
+
specific_dir = input_file_dirs.get(output_key)
|
|
391
|
+
if specific_dir:
|
|
392
|
+
final_path = os.path.join(specific_dir, default_filename)
|
|
393
|
+
logger.debug(f"Using default filename '{default_filename}' in specific input file directory: {specific_dir}")
|
|
394
|
+
# Otherwise use the general input file directory if provided
|
|
395
|
+
elif input_file_dir:
|
|
396
|
+
final_path = os.path.join(input_file_dir, default_filename)
|
|
397
|
+
logger.debug(f"Using default filename '{default_filename}' in input file directory: {input_file_dir}")
|
|
398
|
+
else:
|
|
399
|
+
final_path = default_filename # Relative to CWD initially
|
|
400
|
+
logger.debug(f"Using default filename '{default_filename}' in current directory.")
|
|
321
401
|
|
|
322
402
|
# Resolve to absolute path
|
|
323
403
|
if final_path:
|
|
@@ -340,6 +420,9 @@ def generate_output_paths(
|
|
|
340
420
|
|
|
341
421
|
# --- Example Usage (for testing) ---
|
|
342
422
|
if __name__ == '__main__':
|
|
423
|
+
# Configure logging for standalone execution
|
|
424
|
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
425
|
+
|
|
343
426
|
# Mock inputs
|
|
344
427
|
mock_basename = "my_module"
|
|
345
428
|
mock_language = "python"
|
|
@@ -568,4 +651,4 @@ if __name__ == '__main__':
|
|
|
568
651
|
# 'output_code': '/path/to/cwd/another_module_verify_verified.py',
|
|
569
652
|
# 'output_program': f'/path/to/cwd/{env_verify_prog_path}'
|
|
570
653
|
# }
|
|
571
|
-
del os.environ['PDD_VERIFY_PROGRAM_OUTPUT_PATH'] # Clean up
|
|
654
|
+
del os.environ['PDD_VERIFY_PROGRAM_OUTPUT_PATH'] # Clean up
|
pdd/generate_test.py
CHANGED
|
@@ -15,11 +15,14 @@ console = Console()
|
|
|
15
15
|
def generate_test(
|
|
16
16
|
prompt: str,
|
|
17
17
|
code: str,
|
|
18
|
-
strength: float=DEFAULT_STRENGTH,
|
|
19
|
-
temperature: float=0.0,
|
|
18
|
+
strength: float = DEFAULT_STRENGTH,
|
|
19
|
+
temperature: float = 0.0,
|
|
20
20
|
time: float = DEFAULT_TIME,
|
|
21
21
|
language: str = "python",
|
|
22
|
-
verbose: bool = False
|
|
22
|
+
verbose: bool = False,
|
|
23
|
+
source_file_path: Optional[str] = None,
|
|
24
|
+
test_file_path: Optional[str] = None,
|
|
25
|
+
module_name: Optional[str] = None
|
|
23
26
|
) -> Tuple[str, float, str]:
|
|
24
27
|
"""
|
|
25
28
|
Generate a unit test from a code file using LLM.
|
|
@@ -32,6 +35,9 @@ def generate_test(
|
|
|
32
35
|
language (str): The programming language for the unit test.
|
|
33
36
|
time (float, optional): Time budget for LLM calls. Defaults to DEFAULT_TIME.
|
|
34
37
|
verbose (bool): Whether to print detailed information.
|
|
38
|
+
source_file_path (Optional[str]): Absolute or relative path to the code under test.
|
|
39
|
+
test_file_path (Optional[str]): Destination path for the generated test file.
|
|
40
|
+
module_name (Optional[str]): Module name (without extension) for proper imports.
|
|
35
41
|
|
|
36
42
|
Returns:
|
|
37
43
|
Tuple[str, float, str]: (unit_test, total_cost, model_name)
|
|
@@ -53,7 +59,10 @@ def generate_test(
|
|
|
53
59
|
input_json = {
|
|
54
60
|
"prompt_that_generated_code": processed_prompt,
|
|
55
61
|
"code": code,
|
|
56
|
-
"language": language
|
|
62
|
+
"language": language,
|
|
63
|
+
"source_file_path": source_file_path or "",
|
|
64
|
+
"test_file_path": test_file_path or "",
|
|
65
|
+
"module_name": module_name or ""
|
|
57
66
|
}
|
|
58
67
|
|
|
59
68
|
if verbose:
|
|
@@ -98,6 +107,7 @@ def generate_test(
|
|
|
98
107
|
strength=strength,
|
|
99
108
|
temperature=temperature,
|
|
100
109
|
time=time,
|
|
110
|
+
language=language,
|
|
101
111
|
verbose=verbose
|
|
102
112
|
)
|
|
103
113
|
total_cost += check_cost
|
|
@@ -112,6 +122,7 @@ def generate_test(
|
|
|
112
122
|
strength=strength,
|
|
113
123
|
temperature=temperature,
|
|
114
124
|
time=time,
|
|
125
|
+
language=language,
|
|
115
126
|
verbose=verbose
|
|
116
127
|
)
|
|
117
128
|
total_cost += continue_cost
|
|
@@ -181,4 +192,4 @@ def _validate_inputs(
|
|
|
181
192
|
if not isinstance(temperature, float):
|
|
182
193
|
raise ValueError("Temperature must be a float")
|
|
183
194
|
if not language or not isinstance(language, str):
|
|
184
|
-
raise ValueError("Language must be a non-empty string")
|
|
195
|
+
raise ValueError("Language must be a non-empty string")
|
pdd/get_jwt_token.py
CHANGED
|
@@ -248,7 +248,14 @@ class FirebaseAuthenticator:
|
|
|
248
248
|
except requests.exceptions.ConnectionError as e:
|
|
249
249
|
raise NetworkError(f"Failed to connect to Firebase: {e}")
|
|
250
250
|
except requests.exceptions.RequestException as e:
|
|
251
|
-
|
|
251
|
+
# Capture more detail to help diagnose provider configuration or audience mismatches
|
|
252
|
+
extra = ""
|
|
253
|
+
if getattr(e, "response", None) is not None:
|
|
254
|
+
try:
|
|
255
|
+
extra = f" | response: {e.response.text}"
|
|
256
|
+
except Exception:
|
|
257
|
+
pass
|
|
258
|
+
raise TokenError(f"Error exchanging GitHub token for Firebase token: {e}{extra}")
|
|
252
259
|
|
|
253
260
|
def verify_firebase_token(self, id_token: str) -> bool:
|
|
254
261
|
"""
|
|
@@ -319,4 +326,4 @@ async def get_jwt_token(firebase_api_key: str, github_client_id: str, app_name:
|
|
|
319
326
|
# Store refresh token
|
|
320
327
|
firebase_auth._store_refresh_token(refresh_token)
|
|
321
328
|
|
|
322
|
-
return id_token
|
|
329
|
+
return id_token
|
pdd/get_run_command.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Module to retrieve run commands for programming languages."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import csv
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_run_command(extension: str) -> str:
|
|
8
|
+
"""
|
|
9
|
+
Retrieves the run command for a given file extension.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
extension: The file extension (e.g., ".py", ".js").
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
The run command template with {file} placeholder (e.g., "python {file}"),
|
|
16
|
+
or an empty string if not found or not executable.
|
|
17
|
+
|
|
18
|
+
Raises:
|
|
19
|
+
ValueError: If the PDD_PATH environment variable is not set.
|
|
20
|
+
"""
|
|
21
|
+
# Step 1: Load environment variable PDD_PATH
|
|
22
|
+
pdd_path = os.environ.get('PDD_PATH')
|
|
23
|
+
if not pdd_path:
|
|
24
|
+
raise ValueError("PDD_PATH environment variable is not set")
|
|
25
|
+
|
|
26
|
+
# Step 2: Ensure the extension starts with a dot and convert to lowercase
|
|
27
|
+
if not extension.startswith('.'):
|
|
28
|
+
extension = '.' + extension
|
|
29
|
+
extension = extension.lower()
|
|
30
|
+
|
|
31
|
+
# Step 3: Look up the run command
|
|
32
|
+
csv_path = os.path.join(pdd_path, 'data', 'language_format.csv')
|
|
33
|
+
try:
|
|
34
|
+
with open(csv_path, 'r') as csvfile:
|
|
35
|
+
reader = csv.DictReader(csvfile)
|
|
36
|
+
for row in reader:
|
|
37
|
+
if row['extension'].lower() == extension:
|
|
38
|
+
run_command = row.get('run_command', '').strip()
|
|
39
|
+
return run_command if run_command else ''
|
|
40
|
+
except FileNotFoundError:
|
|
41
|
+
print(f"CSV file not found at {csv_path}")
|
|
42
|
+
except csv.Error as e:
|
|
43
|
+
print(f"Error reading CSV file: {e}")
|
|
44
|
+
except KeyError:
|
|
45
|
+
# run_command column doesn't exist
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
return ''
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_run_command_for_file(file_path: str) -> str:
|
|
52
|
+
"""
|
|
53
|
+
Retrieves the run command for a given file, with the {file} placeholder replaced.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
file_path: The path to the file to run.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
The complete run command (e.g., "python /path/to/script.py"),
|
|
60
|
+
or an empty string if no run command is available for this file type.
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
ValueError: If the PDD_PATH environment variable is not set.
|
|
64
|
+
"""
|
|
65
|
+
_, extension = os.path.splitext(file_path)
|
|
66
|
+
if not extension:
|
|
67
|
+
return ''
|
|
68
|
+
|
|
69
|
+
run_command_template = get_run_command(extension)
|
|
70
|
+
if not run_command_template:
|
|
71
|
+
return ''
|
|
72
|
+
|
|
73
|
+
return run_command_template.replace('{file}', file_path)
|
pdd/get_test_command.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# pdd/get_test_command.py
|
|
2
|
+
"""Get language-appropriate test commands.
|
|
3
|
+
|
|
4
|
+
This module provides functions to resolve the appropriate test command
|
|
5
|
+
for a given test file based on:
|
|
6
|
+
1. CSV run_test_command (if non-empty)
|
|
7
|
+
2. Smart detection via default_verify_cmd_for()
|
|
8
|
+
3. None (triggers agentic fallback)
|
|
9
|
+
"""
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
12
|
+
import csv
|
|
13
|
+
|
|
14
|
+
from .agentic_langtest import default_verify_cmd_for
|
|
15
|
+
from .get_language import get_language
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _load_language_format() -> dict:
|
|
19
|
+
"""Load language_format.csv into a dict keyed by extension."""
|
|
20
|
+
csv_path = Path(__file__).parent.parent / "data" / "language_format.csv"
|
|
21
|
+
result = {}
|
|
22
|
+
with open(csv_path, 'r') as f:
|
|
23
|
+
reader = csv.DictReader(f)
|
|
24
|
+
for row in reader:
|
|
25
|
+
ext = row.get('extension', '')
|
|
26
|
+
if ext:
|
|
27
|
+
result[ext] = row
|
|
28
|
+
return result
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_test_command_for_file(test_file: str, language: Optional[str] = None) -> Optional[str]:
|
|
32
|
+
"""
|
|
33
|
+
Get the appropriate test command for a test file.
|
|
34
|
+
|
|
35
|
+
Resolution order:
|
|
36
|
+
1. CSV run_test_command (if non-empty)
|
|
37
|
+
2. Smart detection via default_verify_cmd_for()
|
|
38
|
+
3. None (triggers agentic fallback)
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
test_file: Path to the test file
|
|
42
|
+
language: Optional language override
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Test command string with {file} placeholder replaced, or None
|
|
46
|
+
"""
|
|
47
|
+
test_path = Path(test_file)
|
|
48
|
+
ext = test_path.suffix
|
|
49
|
+
|
|
50
|
+
resolved_language = language
|
|
51
|
+
if resolved_language is None:
|
|
52
|
+
resolved_language = get_language(ext)
|
|
53
|
+
|
|
54
|
+
# 1. Check CSV for run_test_command
|
|
55
|
+
lang_formats = _load_language_format()
|
|
56
|
+
if ext in lang_formats:
|
|
57
|
+
csv_cmd = lang_formats[ext].get('run_test_command', '').strip()
|
|
58
|
+
if csv_cmd:
|
|
59
|
+
return csv_cmd.replace('{file}', str(test_file))
|
|
60
|
+
|
|
61
|
+
# 2. Smart detection
|
|
62
|
+
if resolved_language:
|
|
63
|
+
smart_cmd = default_verify_cmd_for(resolved_language.lower(), str(test_file))
|
|
64
|
+
if smart_cmd:
|
|
65
|
+
return smart_cmd
|
|
66
|
+
|
|
67
|
+
# 3. No command available
|
|
68
|
+
return None
|
pdd/git_update.py
CHANGED
|
@@ -4,6 +4,8 @@ from rich import print
|
|
|
4
4
|
from rich.console import Console
|
|
5
5
|
from rich.panel import Panel
|
|
6
6
|
from .update_prompt import update_prompt
|
|
7
|
+
from .agentic_common import get_available_agents
|
|
8
|
+
from .agentic_update import run_agentic_update
|
|
7
9
|
import git
|
|
8
10
|
from . import DEFAULT_TIME
|
|
9
11
|
console = Console()
|
|
@@ -14,21 +16,33 @@ def git_update(
|
|
|
14
16
|
strength: float,
|
|
15
17
|
temperature: float,
|
|
16
18
|
verbose: bool = False,
|
|
17
|
-
time: float = DEFAULT_TIME
|
|
19
|
+
time: float = DEFAULT_TIME,
|
|
20
|
+
simple: bool = False,
|
|
21
|
+
quiet: bool = False,
|
|
22
|
+
prompt_file: Optional[str] = None
|
|
18
23
|
) -> Tuple[Optional[str], float, str]:
|
|
19
24
|
"""
|
|
20
|
-
Read in modified code, restore the prior checked-in version from
|
|
21
|
-
update the prompt, write back the modified code,
|
|
25
|
+
Read in modified code, restore the prior checked-in version from Git,
|
|
26
|
+
update the prompt (via agentic or legacy path), write back the modified code,
|
|
27
|
+
and return outputs.
|
|
22
28
|
|
|
23
29
|
Args:
|
|
24
|
-
input_prompt (str): The prompt
|
|
30
|
+
input_prompt (str): The prompt TEXT content (not a file path).
|
|
25
31
|
modified_code_file (str): Filepath of the modified code.
|
|
26
32
|
strength (float): Strength parameter for the LLM model.
|
|
27
33
|
temperature (float): Temperature parameter for the LLM model.
|
|
34
|
+
verbose (bool): Enable verbose logging.
|
|
35
|
+
time (float): Time parameter for the LLM model.
|
|
36
|
+
simple (bool): If True, skip agentic and use legacy update_prompt().
|
|
37
|
+
quiet (bool): Suppress non-error logging.
|
|
38
|
+
prompt_file (Optional[str]): Path to prompt file (required for agentic path).
|
|
28
39
|
|
|
29
40
|
Returns:
|
|
30
|
-
Tuple[Optional[str], float, str]:
|
|
41
|
+
Tuple[Optional[str], float, str]: Updated prompt content, total cost, and model name.
|
|
31
42
|
"""
|
|
43
|
+
modified_code: Optional[str] = None
|
|
44
|
+
agentic_cost = 0.0
|
|
45
|
+
|
|
32
46
|
try:
|
|
33
47
|
# Check if inputs are valid
|
|
34
48
|
if not input_prompt or not modified_code_file:
|
|
@@ -37,26 +51,57 @@ def git_update(
|
|
|
37
51
|
if not os.path.exists(modified_code_file):
|
|
38
52
|
raise FileNotFoundError(f"Modified code file not found: {modified_code_file}")
|
|
39
53
|
|
|
40
|
-
# Initialize git repository
|
|
41
|
-
repo = git.Repo(search_parent_directories=True)
|
|
54
|
+
# Initialize git repository object once
|
|
55
|
+
repo = git.Repo(modified_code_file, search_parent_directories=True)
|
|
56
|
+
repo_root = repo.working_tree_dir
|
|
42
57
|
|
|
43
58
|
# Get the file's relative path to the repo root
|
|
44
|
-
repo_root = repo.git.rev_parse("--show-toplevel")
|
|
45
59
|
relative_path = os.path.relpath(modified_code_file, repo_root)
|
|
46
60
|
|
|
47
|
-
# Read the modified code
|
|
61
|
+
# Read the modified code FIRST (before any git operations)
|
|
48
62
|
with open(modified_code_file, 'r') as file:
|
|
49
63
|
modified_code = file.read()
|
|
50
64
|
|
|
51
|
-
# Restore the prior checked-in version
|
|
65
|
+
# Restore the prior checked-in version using the relative path
|
|
52
66
|
repo.git.checkout('HEAD', '--', relative_path)
|
|
53
67
|
|
|
54
68
|
# Read the original input code
|
|
55
69
|
with open(modified_code_file, 'r') as file:
|
|
56
70
|
original_input_code = file.read()
|
|
57
71
|
|
|
58
|
-
#
|
|
59
|
-
|
|
72
|
+
# Routing decision: agentic vs legacy
|
|
73
|
+
use_agentic = (
|
|
74
|
+
not simple
|
|
75
|
+
and prompt_file is not None
|
|
76
|
+
and get_available_agents()
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if use_agentic:
|
|
80
|
+
# Agentic path
|
|
81
|
+
success, message, agentic_cost, provider, changed_files = run_agentic_update(
|
|
82
|
+
prompt_file=prompt_file,
|
|
83
|
+
code_file=modified_code_file,
|
|
84
|
+
verbose=verbose,
|
|
85
|
+
quiet=quiet
|
|
86
|
+
)
|
|
87
|
+
if success:
|
|
88
|
+
# Read updated prompt content from file
|
|
89
|
+
with open(prompt_file, 'r') as file:
|
|
90
|
+
updated_prompt = file.read()
|
|
91
|
+
|
|
92
|
+
# Pretty print the results
|
|
93
|
+
console.print(Panel.fit(
|
|
94
|
+
f"[bold green]Success (agentic):[/bold green]\n"
|
|
95
|
+
f"Provider: {provider}\n"
|
|
96
|
+
f"Total cost: ${agentic_cost:.6f}\n"
|
|
97
|
+
f"Changed files: {', '.join(changed_files)}"
|
|
98
|
+
))
|
|
99
|
+
|
|
100
|
+
return updated_prompt, agentic_cost, provider
|
|
101
|
+
# Fall through to legacy on agentic failure
|
|
102
|
+
|
|
103
|
+
# Legacy path
|
|
104
|
+
result_prompt, legacy_cost, model_name = update_prompt(
|
|
60
105
|
input_prompt=input_prompt,
|
|
61
106
|
input_code=original_input_code,
|
|
62
107
|
modified_code=modified_code,
|
|
@@ -66,21 +111,27 @@ def git_update(
|
|
|
66
111
|
time=time
|
|
67
112
|
)
|
|
68
113
|
|
|
69
|
-
|
|
70
|
-
with open(modified_code_file, 'w') as file:
|
|
71
|
-
file.write(modified_code)
|
|
72
|
-
|
|
114
|
+
total_cost = agentic_cost + legacy_cost
|
|
73
115
|
|
|
74
116
|
# Pretty print the results
|
|
75
117
|
console.print(Panel.fit(
|
|
76
118
|
f"[bold green]Success:[/bold green]\n"
|
|
77
|
-
f"Modified prompt: {
|
|
119
|
+
f"Modified prompt: {result_prompt}\n"
|
|
78
120
|
f"Total cost: ${total_cost:.6f}\n"
|
|
79
121
|
f"Model name: {model_name}"
|
|
80
122
|
))
|
|
81
123
|
|
|
82
|
-
return
|
|
124
|
+
return result_prompt, total_cost, model_name
|
|
83
125
|
|
|
84
126
|
except Exception as e:
|
|
85
127
|
console.print(Panel(f"[bold red]Error:[/bold red] {str(e)}", title="Error", expand=False))
|
|
86
|
-
return None,
|
|
128
|
+
return None, agentic_cost, ""
|
|
129
|
+
|
|
130
|
+
finally:
|
|
131
|
+
# Always restore user's modified code to disk before returning
|
|
132
|
+
if modified_code is not None and modified_code_file:
|
|
133
|
+
try:
|
|
134
|
+
with open(modified_code_file, 'w') as file:
|
|
135
|
+
file.write(modified_code)
|
|
136
|
+
except Exception:
|
|
137
|
+
pass # Best effort restoration
|
|
@@ -59,8 +59,8 @@ def incremental_code_generator(
|
|
|
59
59
|
if not original_prompt or not new_prompt or not existing_code or not language:
|
|
60
60
|
raise ValueError("All required inputs (original_prompt, new_prompt, existing_code, language) must be provided.")
|
|
61
61
|
|
|
62
|
-
if not 0 <= strength <= 1 or not 0 <= temperature <=
|
|
63
|
-
raise ValueError("Strength
|
|
62
|
+
if not 0 <= strength <= 1 or not 0 <= temperature <= 2 or not 0 <= time <= 1:
|
|
63
|
+
raise ValueError("Strength and time must be between 0 and 1. Temperature must be between 0 and 2.")
|
|
64
64
|
|
|
65
65
|
try:
|
|
66
66
|
total_cost = 0.0
|