pdd-cli 0.0.41__py3-none-any.whl → 0.0.42__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 +1 -1
- pdd/auto_deps_main.py +1 -1
- pdd/bug_main.py +1 -1
- pdd/change_main.py +1 -1
- pdd/cli.py +81 -3
- pdd/cmd_test_main.py +3 -3
- pdd/code_generator_main.py +3 -2
- pdd/conflicts_main.py +1 -1
- pdd/construct_paths.py +221 -19
- pdd/context_generator_main.py +27 -12
- pdd/crash_main.py +44 -51
- pdd/detect_change_main.py +1 -1
- pdd/fix_code_module_errors.py +12 -0
- pdd/fix_main.py +2 -2
- pdd/fix_verification_main.py +1 -1
- pdd/generate_output_paths.py +113 -21
- pdd/generate_test.py +53 -16
- pdd/llm_invoke.py +162 -0
- pdd/preprocess_main.py +1 -1
- pdd/prompts/sync_analysis_LLM.prompt +4 -4
- pdd/split_main.py +1 -1
- pdd/sync_determine_operation.py +921 -456
- pdd/sync_main.py +333 -0
- pdd/sync_orchestration.py +639 -0
- pdd/trace_main.py +1 -1
- pdd/update_main.py +7 -2
- pdd_cli-0.0.42.dist-info/METADATA +307 -0
- {pdd_cli-0.0.41.dist-info → pdd_cli-0.0.42.dist-info}/RECORD +32 -30
- pdd_cli-0.0.41.dist-info/METADATA +0 -269
- {pdd_cli-0.0.41.dist-info → pdd_cli-0.0.42.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.41.dist-info → pdd_cli-0.0.42.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.41.dist-info → pdd_cli-0.0.42.dist-info}/licenses/LICENSE +0 -0
- {pdd_cli-0.0.41.dist-info → pdd_cli-0.0.42.dist-info}/top_level.txt +0 -0
pdd/__init__.py
CHANGED
pdd/auto_deps_main.py
CHANGED
|
@@ -44,7 +44,7 @@ def auto_deps_main( # pylint: disable=too-many-arguments, too-many-locals
|
|
|
44
44
|
"csv": auto_deps_csv_path
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
input_strings, output_file_paths, _ = construct_paths(
|
|
47
|
+
resolved_config, input_strings, output_file_paths, _ = construct_paths(
|
|
48
48
|
input_file_paths=input_file_paths,
|
|
49
49
|
force=ctx.obj.get('force', False),
|
|
50
50
|
quiet=ctx.obj.get('quiet', False),
|
pdd/bug_main.py
CHANGED
|
@@ -45,7 +45,7 @@ def bug_main(
|
|
|
45
45
|
"output": output,
|
|
46
46
|
"language": language
|
|
47
47
|
}
|
|
48
|
-
input_strings, output_file_paths, detected_language = construct_paths(
|
|
48
|
+
resolved_config, input_strings, output_file_paths, detected_language = construct_paths(
|
|
49
49
|
input_file_paths=input_file_paths,
|
|
50
50
|
force=ctx.obj.get('force', False),
|
|
51
51
|
quiet=ctx.obj.get('quiet', False),
|
pdd/change_main.py
CHANGED
|
@@ -197,7 +197,7 @@ def change_main(
|
|
|
197
197
|
logger.debug("Calling construct_paths with inputs: %s and options: %s",
|
|
198
198
|
input_file_paths, command_options)
|
|
199
199
|
try:
|
|
200
|
-
input_strings, output_file_paths, language = construct_paths(
|
|
200
|
+
resolved_config, input_strings, output_file_paths, language = construct_paths(
|
|
201
201
|
input_file_paths=input_file_paths,
|
|
202
202
|
force=force,
|
|
203
203
|
quiet=quiet,
|
pdd/cli.py
CHANGED
|
@@ -35,6 +35,7 @@ from .fix_verification_main import fix_verification_main
|
|
|
35
35
|
from .install_completion import install_completion, get_local_pdd_path
|
|
36
36
|
from .preprocess_main import preprocess_main
|
|
37
37
|
from .split_main import split_main
|
|
38
|
+
from .sync_main import sync_main
|
|
38
39
|
from .trace_main import trace_main
|
|
39
40
|
from .track_cost import track_cost
|
|
40
41
|
from .update_main import update_main
|
|
@@ -695,7 +696,7 @@ def change(
|
|
|
695
696
|
"--output",
|
|
696
697
|
type=click.Path(writable=True),
|
|
697
698
|
default=None,
|
|
698
|
-
help="Specify where to save the updated prompt file
|
|
699
|
+
help="Specify where to save the updated prompt file. If not specified, overwrites the original prompt file to maintain it as the source of truth.",
|
|
699
700
|
)
|
|
700
701
|
@click.option(
|
|
701
702
|
"--git",
|
|
@@ -870,8 +871,8 @@ def crash(
|
|
|
870
871
|
result_data = {
|
|
871
872
|
"success": success,
|
|
872
873
|
"attempts": attempts,
|
|
873
|
-
"
|
|
874
|
-
"
|
|
874
|
+
"fixed_code": fixed_code,
|
|
875
|
+
"fixed_program": fixed_program,
|
|
875
876
|
}
|
|
876
877
|
return result_data, cost, model
|
|
877
878
|
except Exception as e:
|
|
@@ -1098,6 +1099,83 @@ def verify(
|
|
|
1098
1099
|
return None # Return None on failure
|
|
1099
1100
|
|
|
1100
1101
|
|
|
1102
|
+
@cli.command("sync")
|
|
1103
|
+
@click.argument("basename", type=str)
|
|
1104
|
+
@click.option(
|
|
1105
|
+
"--max-attempts",
|
|
1106
|
+
type=int,
|
|
1107
|
+
default=3,
|
|
1108
|
+
show_default=True,
|
|
1109
|
+
help="Maximum number of sync attempts.",
|
|
1110
|
+
)
|
|
1111
|
+
@click.option(
|
|
1112
|
+
"--budget",
|
|
1113
|
+
type=float,
|
|
1114
|
+
default=10.0,
|
|
1115
|
+
show_default=True,
|
|
1116
|
+
help="Maximum total cost allowed for the entire sync process.",
|
|
1117
|
+
)
|
|
1118
|
+
@click.option(
|
|
1119
|
+
"--skip-verify",
|
|
1120
|
+
is_flag=True,
|
|
1121
|
+
default=False,
|
|
1122
|
+
help="Skip verification step during sync.",
|
|
1123
|
+
)
|
|
1124
|
+
@click.option(
|
|
1125
|
+
"--skip-tests",
|
|
1126
|
+
is_flag=True,
|
|
1127
|
+
default=False,
|
|
1128
|
+
help="Skip test generation during sync.",
|
|
1129
|
+
)
|
|
1130
|
+
@click.option(
|
|
1131
|
+
"--target-coverage",
|
|
1132
|
+
type=click.FloatRange(0.0, 100.0),
|
|
1133
|
+
default=90.0,
|
|
1134
|
+
show_default=True,
|
|
1135
|
+
help="Target code coverage percentage for generated tests.",
|
|
1136
|
+
)
|
|
1137
|
+
@click.option(
|
|
1138
|
+
"--log",
|
|
1139
|
+
is_flag=True,
|
|
1140
|
+
default=False,
|
|
1141
|
+
help="Enable detailed logging during sync.",
|
|
1142
|
+
)
|
|
1143
|
+
@click.pass_context
|
|
1144
|
+
@track_cost
|
|
1145
|
+
def sync(
|
|
1146
|
+
ctx: click.Context,
|
|
1147
|
+
basename: str,
|
|
1148
|
+
max_attempts: int,
|
|
1149
|
+
budget: float,
|
|
1150
|
+
skip_verify: bool,
|
|
1151
|
+
skip_tests: bool,
|
|
1152
|
+
target_coverage: float,
|
|
1153
|
+
log: bool,
|
|
1154
|
+
) -> Optional[Tuple[Dict[str, Any], float, str]]:
|
|
1155
|
+
"""Automatically execute the complete PDD workflow loop for a given basename.
|
|
1156
|
+
|
|
1157
|
+
This command implements the entire synchronized cycle, intelligently determining
|
|
1158
|
+
what steps are needed and executing them in the correct order. It detects
|
|
1159
|
+
programming languages by scanning for prompt files matching the pattern
|
|
1160
|
+
{basename}_{language}.prompt in the prompts directory.
|
|
1161
|
+
"""
|
|
1162
|
+
try:
|
|
1163
|
+
results, total_cost, model = sync_main(
|
|
1164
|
+
ctx=ctx,
|
|
1165
|
+
basename=basename,
|
|
1166
|
+
max_attempts=max_attempts,
|
|
1167
|
+
budget=budget,
|
|
1168
|
+
skip_verify=skip_verify,
|
|
1169
|
+
skip_tests=skip_tests,
|
|
1170
|
+
target_coverage=target_coverage,
|
|
1171
|
+
log=log,
|
|
1172
|
+
)
|
|
1173
|
+
return results, total_cost, model
|
|
1174
|
+
except Exception as exception:
|
|
1175
|
+
handle_error(exception, "sync", ctx.obj.get("quiet", False))
|
|
1176
|
+
return None
|
|
1177
|
+
|
|
1178
|
+
|
|
1101
1179
|
@cli.command("install_completion")
|
|
1102
1180
|
@click.pass_context
|
|
1103
1181
|
# No @track_cost
|
pdd/cmd_test_main.py
CHANGED
|
@@ -81,7 +81,7 @@ def cmd_test_main(
|
|
|
81
81
|
"target_coverage": target_coverage,
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
input_strings, output_file_paths, language = construct_paths(
|
|
84
|
+
resolved_config, input_strings, output_file_paths, language = construct_paths(
|
|
85
85
|
input_file_paths=input_file_paths,
|
|
86
86
|
force=ctx.obj["force"],
|
|
87
87
|
quiet=ctx.obj["quiet"],
|
|
@@ -146,8 +146,8 @@ def cmd_test_main(
|
|
|
146
146
|
ctx.exit(1)
|
|
147
147
|
return "", 0.0, ""
|
|
148
148
|
|
|
149
|
-
# Handle output
|
|
150
|
-
output_file = output_file_paths["output"]
|
|
149
|
+
# Handle output - prioritize orchestration output path over construct_paths result
|
|
150
|
+
output_file = output or output_file_paths["output"]
|
|
151
151
|
if merge and existing_tests:
|
|
152
152
|
output_file = existing_tests
|
|
153
153
|
|
pdd/code_generator_main.py
CHANGED
|
@@ -157,7 +157,7 @@ def code_generator_main(
|
|
|
157
157
|
command_options: Dict[str, Any] = {"output": output}
|
|
158
158
|
|
|
159
159
|
try:
|
|
160
|
-
input_strings, output_file_paths, language = construct_paths(
|
|
160
|
+
resolved_config, input_strings, output_file_paths, language = construct_paths(
|
|
161
161
|
input_file_paths=input_file_paths_dict,
|
|
162
162
|
force=force_overwrite,
|
|
163
163
|
quiet=quiet,
|
|
@@ -165,7 +165,8 @@ def code_generator_main(
|
|
|
165
165
|
command_options=command_options,
|
|
166
166
|
)
|
|
167
167
|
prompt_content = input_strings["prompt_file"]
|
|
168
|
-
|
|
168
|
+
# Prioritize orchestration output path over construct_paths result
|
|
169
|
+
output_path = output or output_file_paths.get("output")
|
|
169
170
|
|
|
170
171
|
except FileNotFoundError as e:
|
|
171
172
|
console.print(f"[red]Error: Input file not found: {e.filename}[/red]")
|
pdd/conflicts_main.py
CHANGED
|
@@ -28,7 +28,7 @@ def conflicts_main(ctx: click.Context, prompt1: str, prompt2: str, output: Optio
|
|
|
28
28
|
command_options = {
|
|
29
29
|
"output": output
|
|
30
30
|
}
|
|
31
|
-
input_strings, output_file_paths, _ = construct_paths(
|
|
31
|
+
resolved_config, input_strings, output_file_paths, _ = construct_paths(
|
|
32
32
|
input_file_paths=input_file_paths,
|
|
33
33
|
force=ctx.obj.get('force', False),
|
|
34
34
|
quiet=ctx.obj.get('quiet', False),
|
pdd/construct_paths.py
CHANGED
|
@@ -4,9 +4,12 @@ from __future__ import annotations
|
|
|
4
4
|
import sys
|
|
5
5
|
import os
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Dict, Tuple, Any, Optional
|
|
7
|
+
from typing import Dict, Tuple, Any, Optional, List
|
|
8
|
+
import fnmatch
|
|
9
|
+
import logging
|
|
8
10
|
|
|
9
11
|
import click
|
|
12
|
+
import yaml
|
|
10
13
|
from rich.console import Console
|
|
11
14
|
from rich.theme import Theme
|
|
12
15
|
|
|
@@ -21,6 +24,114 @@ import csv
|
|
|
21
24
|
|
|
22
25
|
console = Console(theme=Theme({"info": "cyan", "warning": "yellow", "error": "bold red"}))
|
|
23
26
|
|
|
27
|
+
# Configuration loading functions
|
|
28
|
+
def _find_pddrc_file(start_path: Optional[Path] = None) -> Optional[Path]:
|
|
29
|
+
"""Find .pddrc file by searching upward from the given path."""
|
|
30
|
+
if start_path is None:
|
|
31
|
+
start_path = Path.cwd()
|
|
32
|
+
|
|
33
|
+
# Search upward through parent directories
|
|
34
|
+
for path in [start_path] + list(start_path.parents):
|
|
35
|
+
pddrc_file = path / ".pddrc"
|
|
36
|
+
if pddrc_file.is_file():
|
|
37
|
+
return pddrc_file
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
def _load_pddrc_config(pddrc_path: Path) -> Dict[str, Any]:
|
|
41
|
+
"""Load and parse .pddrc configuration file."""
|
|
42
|
+
try:
|
|
43
|
+
with open(pddrc_path, 'r', encoding='utf-8') as f:
|
|
44
|
+
config = yaml.safe_load(f)
|
|
45
|
+
|
|
46
|
+
if not isinstance(config, dict):
|
|
47
|
+
raise ValueError(f"Invalid .pddrc format: expected dictionary at root level")
|
|
48
|
+
|
|
49
|
+
# Validate basic structure
|
|
50
|
+
if 'contexts' not in config:
|
|
51
|
+
raise ValueError(f"Invalid .pddrc format: missing 'contexts' section")
|
|
52
|
+
|
|
53
|
+
return config
|
|
54
|
+
except yaml.YAMLError as e:
|
|
55
|
+
raise ValueError(f"YAML syntax error in .pddrc: {e}")
|
|
56
|
+
except Exception as e:
|
|
57
|
+
raise ValueError(f"Error loading .pddrc: {e}")
|
|
58
|
+
|
|
59
|
+
def _detect_context(current_dir: Path, config: Dict[str, Any], context_override: Optional[str] = None) -> Optional[str]:
|
|
60
|
+
"""Detect the appropriate context based on current directory path."""
|
|
61
|
+
if context_override:
|
|
62
|
+
# Validate that the override context exists
|
|
63
|
+
contexts = config.get('contexts', {})
|
|
64
|
+
if context_override not in contexts:
|
|
65
|
+
available = list(contexts.keys())
|
|
66
|
+
raise ValueError(f"Unknown context '{context_override}'. Available contexts: {available}")
|
|
67
|
+
return context_override
|
|
68
|
+
|
|
69
|
+
contexts = config.get('contexts', {})
|
|
70
|
+
current_path_str = str(current_dir)
|
|
71
|
+
|
|
72
|
+
# Try to match against each context's paths
|
|
73
|
+
for context_name, context_config in contexts.items():
|
|
74
|
+
if context_name == 'default':
|
|
75
|
+
continue # Handle default as fallback
|
|
76
|
+
|
|
77
|
+
paths = context_config.get('paths', [])
|
|
78
|
+
for path_pattern in paths:
|
|
79
|
+
# Convert glob pattern to match current directory
|
|
80
|
+
if fnmatch.fnmatch(current_path_str, f"*/{path_pattern}") or \
|
|
81
|
+
fnmatch.fnmatch(current_path_str, path_pattern) or \
|
|
82
|
+
current_path_str.endswith(f"/{path_pattern.rstrip('/**')}"):
|
|
83
|
+
return context_name
|
|
84
|
+
|
|
85
|
+
# Return default context if available
|
|
86
|
+
if 'default' in contexts:
|
|
87
|
+
return 'default'
|
|
88
|
+
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
def _get_context_config(config: Dict[str, Any], context_name: Optional[str]) -> Dict[str, Any]:
|
|
92
|
+
"""Get configuration settings for the specified context."""
|
|
93
|
+
if not context_name:
|
|
94
|
+
return {}
|
|
95
|
+
|
|
96
|
+
contexts = config.get('contexts', {})
|
|
97
|
+
context_config = contexts.get(context_name, {})
|
|
98
|
+
return context_config.get('defaults', {})
|
|
99
|
+
|
|
100
|
+
def _resolve_config_hierarchy(
|
|
101
|
+
cli_options: Dict[str, Any],
|
|
102
|
+
context_config: Dict[str, Any],
|
|
103
|
+
env_vars: Dict[str, str]
|
|
104
|
+
) -> Dict[str, Any]:
|
|
105
|
+
"""Apply configuration hierarchy: CLI > context > environment > defaults."""
|
|
106
|
+
resolved = {}
|
|
107
|
+
|
|
108
|
+
# Configuration keys to resolve
|
|
109
|
+
config_keys = {
|
|
110
|
+
'generate_output_path': 'PDD_GENERATE_OUTPUT_PATH',
|
|
111
|
+
'test_output_path': 'PDD_TEST_OUTPUT_PATH',
|
|
112
|
+
'example_output_path': 'PDD_EXAMPLE_OUTPUT_PATH',
|
|
113
|
+
'default_language': 'PDD_DEFAULT_LANGUAGE',
|
|
114
|
+
'target_coverage': 'PDD_TEST_COVERAGE_TARGET',
|
|
115
|
+
'strength': None,
|
|
116
|
+
'temperature': None,
|
|
117
|
+
'budget': None,
|
|
118
|
+
'max_attempts': None,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for config_key, env_var in config_keys.items():
|
|
122
|
+
# 1. CLI options (highest priority)
|
|
123
|
+
if config_key in cli_options and cli_options[config_key] is not None:
|
|
124
|
+
resolved[config_key] = cli_options[config_key]
|
|
125
|
+
# 2. Context configuration
|
|
126
|
+
elif config_key in context_config:
|
|
127
|
+
resolved[config_key] = context_config[config_key]
|
|
128
|
+
# 3. Environment variables
|
|
129
|
+
elif env_var and env_var in env_vars:
|
|
130
|
+
resolved[config_key] = env_vars[env_var]
|
|
131
|
+
# 4. Defaults are handled elsewhere
|
|
132
|
+
|
|
133
|
+
return resolved
|
|
134
|
+
|
|
24
135
|
|
|
25
136
|
def _read_file(path: Path) -> str:
|
|
26
137
|
"""Read a text file safely and return its contents."""
|
|
@@ -126,29 +237,24 @@ def _is_known_language(language_name: str) -> bool:
|
|
|
126
237
|
|
|
127
238
|
def _strip_language_suffix(path_like: os.PathLike[str]) -> str:
|
|
128
239
|
"""
|
|
129
|
-
Remove trailing '_<language
|
|
130
|
-
if it matches a known language.
|
|
240
|
+
Remove trailing '_<language>' from a filename stem if it matches a known language.
|
|
131
241
|
"""
|
|
132
242
|
p = Path(path_like)
|
|
133
|
-
stem = p.stem
|
|
243
|
+
stem = p.stem # removes last extension (e.g., '.prompt', '.py')
|
|
134
244
|
|
|
135
|
-
if "_" not in stem:
|
|
245
|
+
if "_" not in stem:
|
|
136
246
|
return stem
|
|
137
247
|
|
|
138
248
|
parts = stem.split("_")
|
|
139
|
-
# Avoid splitting single-word stems like "Makefile_" if that's possible
|
|
140
|
-
if len(parts) < 2:
|
|
141
|
-
return stem
|
|
142
|
-
|
|
143
249
|
candidate_lang = parts[-1]
|
|
144
250
|
|
|
145
|
-
# Check if the last part is a known language
|
|
146
251
|
if _is_known_language(candidate_lang):
|
|
147
|
-
#
|
|
252
|
+
# Do not strip '_prompt' from a non-.prompt file (e.g., 'test_prompt.txt')
|
|
253
|
+
if candidate_lang == 'prompt' and p.suffix != '.prompt':
|
|
254
|
+
return stem
|
|
148
255
|
return "_".join(parts[:-1])
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
return stem
|
|
256
|
+
|
|
257
|
+
return stem
|
|
152
258
|
|
|
153
259
|
|
|
154
260
|
def _extract_basename(
|
|
@@ -267,20 +373,101 @@ def construct_paths(
|
|
|
267
373
|
command: str,
|
|
268
374
|
command_options: Optional[Dict[str, Any]], # Allow None
|
|
269
375
|
create_error_file: bool = True, # Added parameter to control error file creation
|
|
270
|
-
|
|
376
|
+
context_override: Optional[str] = None, # Added parameter for context override
|
|
377
|
+
) -> Tuple[Dict[str, Any], Dict[str, str], Dict[str, str], str]:
|
|
271
378
|
"""
|
|
272
379
|
High‑level orchestrator that loads inputs, determines basename/language,
|
|
273
380
|
computes output locations, and verifies overwrite rules.
|
|
381
|
+
|
|
382
|
+
Supports .pddrc configuration with context-aware settings and configuration hierarchy:
|
|
383
|
+
CLI options > .pddrc context > environment variables > defaults
|
|
274
384
|
|
|
275
385
|
Returns
|
|
276
386
|
-------
|
|
277
|
-
(input_strings, output_file_paths, language)
|
|
387
|
+
(resolved_config, input_strings, output_file_paths, language)
|
|
278
388
|
"""
|
|
279
389
|
command_options = command_options or {} # Ensure command_options is a dict
|
|
280
390
|
|
|
391
|
+
# ------------- Load .pddrc configuration -----------------
|
|
392
|
+
pddrc_config = {}
|
|
393
|
+
context = None
|
|
394
|
+
context_config = {}
|
|
395
|
+
|
|
396
|
+
try:
|
|
397
|
+
# Find and load .pddrc file
|
|
398
|
+
pddrc_path = _find_pddrc_file()
|
|
399
|
+
if pddrc_path:
|
|
400
|
+
pddrc_config = _load_pddrc_config(pddrc_path)
|
|
401
|
+
|
|
402
|
+
# Detect appropriate context
|
|
403
|
+
current_dir = Path.cwd()
|
|
404
|
+
context = _detect_context(current_dir, pddrc_config, context_override)
|
|
405
|
+
|
|
406
|
+
# Get context-specific configuration
|
|
407
|
+
context_config = _get_context_config(pddrc_config, context)
|
|
408
|
+
|
|
409
|
+
if not quiet and context:
|
|
410
|
+
console.print(f"[info]Using .pddrc context:[/info] {context}")
|
|
411
|
+
|
|
412
|
+
# Apply configuration hierarchy
|
|
413
|
+
env_vars = dict(os.environ)
|
|
414
|
+
resolved_config = _resolve_config_hierarchy(command_options, context_config, env_vars)
|
|
415
|
+
|
|
416
|
+
# Update command_options with resolved configuration for internal use
|
|
417
|
+
for key, value in resolved_config.items():
|
|
418
|
+
if key not in command_options or command_options[key] is None:
|
|
419
|
+
command_options[key] = value
|
|
420
|
+
|
|
421
|
+
# Also update context_config with resolved environment variables for generate_output_paths
|
|
422
|
+
# This ensures environment variables are available when context config doesn't override them
|
|
423
|
+
for key, value in resolved_config.items():
|
|
424
|
+
if key.endswith('_output_path') and key not in context_config:
|
|
425
|
+
context_config[key] = value
|
|
426
|
+
|
|
427
|
+
except Exception as e:
|
|
428
|
+
error_msg = f"Configuration error: {e}"
|
|
429
|
+
console.print(f"[error]{error_msg}[/error]", style="error")
|
|
430
|
+
if not quiet:
|
|
431
|
+
console.print("[warning]Continuing with default configuration...[/warning]", style="warning")
|
|
432
|
+
# Initialize resolved_config on error to avoid downstream issues
|
|
433
|
+
resolved_config = command_options.copy()
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
# ------------- Handle sync discovery mode ----------------
|
|
437
|
+
if command == "sync" and not input_file_paths:
|
|
438
|
+
basename = command_options.get("basename")
|
|
439
|
+
if not basename:
|
|
440
|
+
raise ValueError("Basename must be provided in command_options for sync discovery mode.")
|
|
441
|
+
|
|
442
|
+
# For discovery, we only need directory paths. Call generate_output_paths with dummy values.
|
|
443
|
+
try:
|
|
444
|
+
output_paths_str = generate_output_paths(
|
|
445
|
+
command="sync",
|
|
446
|
+
output_locations={},
|
|
447
|
+
basename=basename,
|
|
448
|
+
language="python", # Dummy language
|
|
449
|
+
file_extension=".py", # Dummy extension
|
|
450
|
+
context_config=context_config,
|
|
451
|
+
)
|
|
452
|
+
# Infer base directories from a sample output path
|
|
453
|
+
gen_path = Path(output_paths_str.get("generate_output_path", "src"))
|
|
454
|
+
resolved_config["prompts_dir"] = str(gen_path.parent.parent / "prompts")
|
|
455
|
+
resolved_config["code_dir"] = str(gen_path.parent)
|
|
456
|
+
resolved_config["tests_dir"] = str(Path(output_paths_str.get("test_output_path", "tests")).parent)
|
|
457
|
+
resolved_config["examples_dir"] = str(Path(output_paths_str.get("example_output_path", "examples")).parent)
|
|
458
|
+
|
|
459
|
+
except Exception as e:
|
|
460
|
+
console.print(f"[error]Failed to determine initial paths for sync: {e}", style="error")
|
|
461
|
+
raise
|
|
462
|
+
|
|
463
|
+
# Return early for discovery mode
|
|
464
|
+
return resolved_config, {}, {}, ""
|
|
465
|
+
|
|
466
|
+
|
|
281
467
|
if not input_file_paths:
|
|
282
468
|
raise ValueError("No input files provided")
|
|
283
469
|
|
|
470
|
+
|
|
284
471
|
# ------------- normalise & resolve Paths -----------------
|
|
285
472
|
input_paths: Dict[str, Path] = {}
|
|
286
473
|
for key, path_str in input_file_paths.items():
|
|
@@ -407,6 +594,7 @@ def construct_paths(
|
|
|
407
594
|
basename=basename,
|
|
408
595
|
language=language,
|
|
409
596
|
file_extension=file_extension,
|
|
597
|
+
context_config=context_config,
|
|
410
598
|
)
|
|
411
599
|
# Convert to Path objects for internal use
|
|
412
600
|
output_paths_resolved: Dict[str, Path] = {k: Path(v) for k, v in output_paths_str.items()}
|
|
@@ -440,8 +628,12 @@ def construct_paths(
|
|
|
440
628
|
click.secho("Operation cancelled.", fg="red", err=True)
|
|
441
629
|
sys.exit(1) # Exit if user chooses not to overwrite
|
|
442
630
|
except Exception as e: # Catch potential errors during confirm (like EOFError in non-interactive)
|
|
443
|
-
|
|
444
|
-
|
|
631
|
+
if 'EOF' in str(e) or 'end-of-file' in str(e).lower():
|
|
632
|
+
# Non-interactive environment, default to not overwriting
|
|
633
|
+
click.secho("Non-interactive environment detected. Use --force to overwrite existing files.", fg="yellow", err=True)
|
|
634
|
+
else:
|
|
635
|
+
click.secho(f"Confirmation failed: {e}. Aborting.", fg="red", err=True)
|
|
636
|
+
sys.exit(1)
|
|
445
637
|
|
|
446
638
|
|
|
447
639
|
# ------------- Final reporting ---------------------------
|
|
@@ -462,4 +654,14 @@ def construct_paths(
|
|
|
462
654
|
# Since we converted to Path, convert back now.
|
|
463
655
|
output_file_paths_str_return = {k: str(v) for k, v in output_paths_resolved.items()}
|
|
464
656
|
|
|
465
|
-
|
|
657
|
+
# Add resolved paths to the config that gets returned
|
|
658
|
+
resolved_config.update(output_file_paths_str_return)
|
|
659
|
+
# Also add inferred directory paths
|
|
660
|
+
gen_path = Path(resolved_config.get("generate_output_path", "src"))
|
|
661
|
+
resolved_config["prompts_dir"] = str(next(iter(input_paths.values())).parent)
|
|
662
|
+
resolved_config["code_dir"] = str(gen_path.parent)
|
|
663
|
+
resolved_config["tests_dir"] = str(Path(resolved_config.get("test_output_path", "tests")).parent)
|
|
664
|
+
resolved_config["examples_dir"] = str(Path(resolved_config.get("example_output_path", "examples")).parent)
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
return resolved_config, input_strings, output_file_paths_str_return, language
|
pdd/context_generator_main.py
CHANGED
|
@@ -25,7 +25,7 @@ def context_generator_main(ctx: click.Context, prompt_file: str, code_file: str,
|
|
|
25
25
|
command_options = {
|
|
26
26
|
"output": output
|
|
27
27
|
}
|
|
28
|
-
input_strings, output_file_paths, language = construct_paths(
|
|
28
|
+
resolved_config, input_strings, output_file_paths, language = construct_paths(
|
|
29
29
|
input_file_paths=input_file_paths,
|
|
30
30
|
force=ctx.obj.get('force', False),
|
|
31
31
|
quiet=ctx.obj.get('quiet', False),
|
|
@@ -51,22 +51,37 @@ def context_generator_main(ctx: click.Context, prompt_file: str, code_file: str,
|
|
|
51
51
|
verbose=ctx.obj.get('verbose', False)
|
|
52
52
|
)
|
|
53
53
|
|
|
54
|
-
# Save results
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
# Save results - prioritize orchestration output path over construct_paths result
|
|
55
|
+
final_output_path = output or output_file_paths["output"]
|
|
56
|
+
print(f"DEBUG: output param = {output}")
|
|
57
|
+
print(f"DEBUG: output_file_paths['output'] = {output_file_paths['output']}")
|
|
58
|
+
print(f"DEBUG: final_output_path = {final_output_path}")
|
|
59
|
+
if final_output_path and example_code is not None:
|
|
60
|
+
with open(final_output_path, 'w') as f:
|
|
57
61
|
f.write(example_code)
|
|
62
|
+
elif final_output_path and example_code is None:
|
|
63
|
+
# Log the error but don't crash
|
|
64
|
+
if not ctx.obj.get('quiet', False):
|
|
65
|
+
rprint("[bold red]Warning:[/bold red] Example generation failed, skipping file write")
|
|
58
66
|
|
|
59
67
|
# Provide user feedback
|
|
60
68
|
if not ctx.obj.get('quiet', False):
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
69
|
+
if example_code is not None:
|
|
70
|
+
rprint("[bold green]Example code generated successfully.[/bold green]")
|
|
71
|
+
rprint(f"[bold]Model used:[/bold] {model_name}")
|
|
72
|
+
rprint(f"[bold]Total cost:[/bold] ${total_cost:.6f}")
|
|
73
|
+
if final_output_path and example_code is not None:
|
|
74
|
+
rprint(f"[bold]Example code saved to:[/bold] {final_output_path}")
|
|
75
|
+
else:
|
|
76
|
+
rprint("[bold red]Example code generation failed.[/bold red]")
|
|
77
|
+
rprint(f"[bold]Total cost:[/bold] ${total_cost:.6f}")
|
|
66
78
|
|
|
67
|
-
# Always print example code, even in quiet mode
|
|
68
|
-
|
|
69
|
-
|
|
79
|
+
# Always print example code, even in quiet mode (if it exists)
|
|
80
|
+
if example_code is not None:
|
|
81
|
+
rprint("[bold]Generated Example Code:[/bold]")
|
|
82
|
+
rprint(example_code)
|
|
83
|
+
else:
|
|
84
|
+
rprint("[bold red]No example code generated due to errors.[/bold red]")
|
|
70
85
|
|
|
71
86
|
return example_code, total_cost, model_name
|
|
72
87
|
|