pdd-cli 0.0.90__py3-none-any.whl → 0.0.121__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pdd/__init__.py +38 -6
- pdd/agentic_bug.py +323 -0
- pdd/agentic_bug_orchestrator.py +506 -0
- pdd/agentic_change.py +231 -0
- pdd/agentic_change_orchestrator.py +537 -0
- pdd/agentic_common.py +533 -770
- pdd/agentic_crash.py +2 -1
- pdd/agentic_e2e_fix.py +319 -0
- pdd/agentic_e2e_fix_orchestrator.py +582 -0
- pdd/agentic_fix.py +118 -3
- pdd/agentic_update.py +27 -9
- pdd/agentic_verify.py +3 -2
- pdd/architecture_sync.py +565 -0
- pdd/auth_service.py +210 -0
- pdd/auto_deps_main.py +63 -53
- pdd/auto_include.py +236 -3
- pdd/auto_update.py +125 -47
- pdd/bug_main.py +195 -23
- pdd/cmd_test_main.py +345 -197
- pdd/code_generator.py +4 -2
- pdd/code_generator_main.py +118 -32
- pdd/commands/__init__.py +6 -0
- pdd/commands/analysis.py +113 -48
- pdd/commands/auth.py +309 -0
- pdd/commands/connect.py +358 -0
- pdd/commands/fix.py +155 -114
- pdd/commands/generate.py +5 -0
- pdd/commands/maintenance.py +3 -2
- pdd/commands/misc.py +8 -0
- pdd/commands/modify.py +225 -163
- pdd/commands/sessions.py +284 -0
- pdd/commands/utility.py +12 -7
- pdd/construct_paths.py +334 -32
- pdd/context_generator_main.py +167 -170
- pdd/continue_generation.py +6 -3
- pdd/core/__init__.py +33 -0
- pdd/core/cli.py +44 -7
- pdd/core/cloud.py +237 -0
- pdd/core/dump.py +68 -20
- pdd/core/errors.py +4 -0
- pdd/core/remote_session.py +61 -0
- pdd/crash_main.py +219 -23
- pdd/data/llm_model.csv +4 -4
- pdd/docs/prompting_guide.md +864 -0
- pdd/docs/whitepaper_with_benchmarks/data_and_functions/benchmark_analysis.py +495 -0
- pdd/docs/whitepaper_with_benchmarks/data_and_functions/creation_compare.py +528 -0
- pdd/fix_code_loop.py +208 -34
- pdd/fix_code_module_errors.py +6 -2
- pdd/fix_error_loop.py +291 -38
- pdd/fix_main.py +208 -6
- pdd/fix_verification_errors_loop.py +235 -26
- pdd/fix_verification_main.py +269 -83
- pdd/frontend/dist/assets/index-B5DZHykP.css +1 -0
- pdd/frontend/dist/assets/index-CUWd8al1.js +450 -0
- pdd/frontend/dist/index.html +376 -0
- pdd/frontend/dist/logo.svg +33 -0
- pdd/generate_output_paths.py +46 -5
- pdd/generate_test.py +212 -151
- pdd/get_comment.py +19 -44
- pdd/get_extension.py +8 -9
- pdd/get_jwt_token.py +309 -20
- pdd/get_language.py +8 -7
- pdd/get_run_command.py +7 -5
- pdd/insert_includes.py +2 -1
- pdd/llm_invoke.py +531 -97
- pdd/load_prompt_template.py +15 -34
- pdd/operation_log.py +342 -0
- pdd/path_resolution.py +140 -0
- pdd/postprocess.py +122 -97
- pdd/preprocess.py +68 -12
- pdd/preprocess_main.py +33 -1
- pdd/prompts/agentic_bug_step10_pr_LLM.prompt +182 -0
- pdd/prompts/agentic_bug_step1_duplicate_LLM.prompt +73 -0
- pdd/prompts/agentic_bug_step2_docs_LLM.prompt +129 -0
- pdd/prompts/agentic_bug_step3_triage_LLM.prompt +95 -0
- pdd/prompts/agentic_bug_step4_reproduce_LLM.prompt +97 -0
- pdd/prompts/agentic_bug_step5_root_cause_LLM.prompt +123 -0
- pdd/prompts/agentic_bug_step6_test_plan_LLM.prompt +107 -0
- pdd/prompts/agentic_bug_step7_generate_LLM.prompt +172 -0
- pdd/prompts/agentic_bug_step8_verify_LLM.prompt +119 -0
- pdd/prompts/agentic_bug_step9_e2e_test_LLM.prompt +289 -0
- pdd/prompts/agentic_change_step10_identify_issues_LLM.prompt +1006 -0
- pdd/prompts/agentic_change_step11_fix_issues_LLM.prompt +984 -0
- pdd/prompts/agentic_change_step12_create_pr_LLM.prompt +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 +981 -0
- pdd/prompts/agentic_change_step6_devunits_LLM.prompt +1005 -0
- pdd/prompts/agentic_change_step7_architecture_LLM.prompt +1044 -0
- pdd/prompts/agentic_change_step8_analyze_LLM.prompt +1027 -0
- pdd/prompts/agentic_change_step9_implement_LLM.prompt +1077 -0
- pdd/prompts/agentic_e2e_fix_step1_unit_tests_LLM.prompt +90 -0
- pdd/prompts/agentic_e2e_fix_step2_e2e_tests_LLM.prompt +91 -0
- pdd/prompts/agentic_e2e_fix_step3_root_cause_LLM.prompt +89 -0
- pdd/prompts/agentic_e2e_fix_step4_fix_e2e_tests_LLM.prompt +96 -0
- pdd/prompts/agentic_e2e_fix_step5_identify_devunits_LLM.prompt +91 -0
- pdd/prompts/agentic_e2e_fix_step6_create_unit_tests_LLM.prompt +106 -0
- pdd/prompts/agentic_e2e_fix_step7_verify_tests_LLM.prompt +116 -0
- pdd/prompts/agentic_e2e_fix_step8_run_pdd_fix_LLM.prompt +120 -0
- pdd/prompts/agentic_e2e_fix_step9_verify_all_LLM.prompt +146 -0
- pdd/prompts/agentic_fix_primary_LLM.prompt +2 -2
- pdd/prompts/agentic_update_LLM.prompt +192 -338
- pdd/prompts/auto_include_LLM.prompt +22 -0
- pdd/prompts/change_LLM.prompt +3093 -1
- pdd/prompts/detect_change_LLM.prompt +571 -14
- pdd/prompts/fix_code_module_errors_LLM.prompt +8 -0
- pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +1 -0
- pdd/prompts/generate_test_LLM.prompt +19 -1
- pdd/prompts/generate_test_from_example_LLM.prompt +366 -0
- pdd/prompts/insert_includes_LLM.prompt +262 -252
- pdd/prompts/prompt_code_diff_LLM.prompt +123 -0
- pdd/prompts/prompt_diff_LLM.prompt +82 -0
- pdd/remote_session.py +876 -0
- pdd/server/__init__.py +52 -0
- pdd/server/app.py +335 -0
- pdd/server/click_executor.py +587 -0
- pdd/server/executor.py +338 -0
- pdd/server/jobs.py +661 -0
- pdd/server/models.py +241 -0
- pdd/server/routes/__init__.py +31 -0
- pdd/server/routes/architecture.py +451 -0
- pdd/server/routes/auth.py +364 -0
- pdd/server/routes/commands.py +929 -0
- pdd/server/routes/config.py +42 -0
- pdd/server/routes/files.py +603 -0
- pdd/server/routes/prompts.py +1347 -0
- pdd/server/routes/websocket.py +473 -0
- pdd/server/security.py +243 -0
- pdd/server/terminal_spawner.py +217 -0
- pdd/server/token_counter.py +222 -0
- pdd/summarize_directory.py +236 -237
- pdd/sync_animation.py +8 -4
- pdd/sync_determine_operation.py +329 -47
- pdd/sync_main.py +272 -28
- pdd/sync_orchestration.py +289 -211
- pdd/sync_order.py +304 -0
- pdd/template_expander.py +161 -0
- pdd/templates/architecture/architecture_json.prompt +41 -46
- pdd/trace.py +1 -1
- pdd/track_cost.py +0 -13
- pdd/unfinished_prompt.py +2 -1
- pdd/update_main.py +68 -26
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/METADATA +15 -10
- pdd_cli-0.0.121.dist-info/RECORD +229 -0
- pdd_cli-0.0.90.dist-info/RECORD +0 -153
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/licenses/LICENSE +0 -0
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/top_level.txt +0 -0
pdd/sync_main.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fnmatch
|
|
1
2
|
import re
|
|
2
3
|
import time
|
|
3
4
|
from pathlib import Path
|
|
@@ -12,17 +13,26 @@ from rich import print as rprint
|
|
|
12
13
|
# Relative imports from the pdd package
|
|
13
14
|
from . import DEFAULT_STRENGTH, DEFAULT_TIME
|
|
14
15
|
from .construct_paths import (
|
|
15
|
-
_is_known_language,
|
|
16
|
+
_is_known_language,
|
|
16
17
|
construct_paths,
|
|
17
18
|
_find_pddrc_file,
|
|
19
|
+
_get_relative_basename,
|
|
18
20
|
_load_pddrc_config,
|
|
19
21
|
_detect_context,
|
|
20
|
-
_get_context_config
|
|
22
|
+
_get_context_config,
|
|
23
|
+
get_extension
|
|
21
24
|
)
|
|
22
25
|
from .sync_orchestration import sync_orchestration
|
|
26
|
+
from .template_expander import expand_template
|
|
23
27
|
|
|
24
|
-
#
|
|
25
|
-
|
|
28
|
+
# Regex for basename validation supporting subdirectory paths (e.g., 'core/cloud')
|
|
29
|
+
# Allows: alphanumeric, underscore, hyphen, and forward slash for subdirectory paths
|
|
30
|
+
# Structure inherently prevents:
|
|
31
|
+
# - Path traversal (..) - dot not in character class
|
|
32
|
+
# - Leading slash (/abs) - must start with [a-zA-Z0-9_-]+
|
|
33
|
+
# - Trailing slash (path/) - must end with [a-zA-Z0-9_-]+
|
|
34
|
+
# - Double slash (a//b) - requires characters between slashes
|
|
35
|
+
VALID_BASENAME_CHARS = re.compile(r"^[a-zA-Z0-9_-]+(/[a-zA-Z0-9_-]+)*$")
|
|
26
36
|
|
|
27
37
|
|
|
28
38
|
def _validate_basename(basename: str) -> None:
|
|
@@ -32,27 +42,231 @@ def _validate_basename(basename: str) -> None:
|
|
|
32
42
|
if not VALID_BASENAME_CHARS.match(basename):
|
|
33
43
|
raise click.UsageError(
|
|
34
44
|
f"Basename '{basename}' contains invalid characters. "
|
|
35
|
-
"Only alphanumeric, underscore, and
|
|
45
|
+
"Only alphanumeric, underscore, hyphen, and forward slash (for subdirectories) are allowed."
|
|
36
46
|
)
|
|
37
47
|
|
|
38
48
|
|
|
49
|
+
def _get_extension_safe(language: str) -> str:
|
|
50
|
+
"""Get file extension with fallback for when PDD_PATH is not set."""
|
|
51
|
+
try:
|
|
52
|
+
return get_extension(language)
|
|
53
|
+
except (ValueError, FileNotFoundError):
|
|
54
|
+
# Fallback to built-in mapping
|
|
55
|
+
builtin_ext_map = {
|
|
56
|
+
'python': 'py', 'javascript': 'js', 'typescript': 'ts', 'java': 'java',
|
|
57
|
+
'typescriptreact': 'tsx', 'javascriptreact': 'jsx',
|
|
58
|
+
'cpp': 'cpp', 'c': 'c', 'go': 'go', 'ruby': 'rb', 'rust': 'rs',
|
|
59
|
+
}
|
|
60
|
+
return builtin_ext_map.get(language.lower(), '')
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _relative_basename_for_context(basename: str, context_config: Dict[str, Any]) -> str:
|
|
64
|
+
"""Return basename relative to a context's most specific path or prompt prefix."""
|
|
65
|
+
matches = []
|
|
66
|
+
|
|
67
|
+
for path_pattern in context_config.get('paths', []):
|
|
68
|
+
pattern_base = path_pattern.rstrip('/**').rstrip('/*')
|
|
69
|
+
if fnmatch.fnmatch(basename, path_pattern) or \
|
|
70
|
+
basename.startswith(pattern_base + '/') or \
|
|
71
|
+
basename == pattern_base:
|
|
72
|
+
relative = _get_relative_basename(basename, path_pattern)
|
|
73
|
+
matches.append((len(pattern_base), relative))
|
|
74
|
+
|
|
75
|
+
defaults = context_config.get('defaults', {})
|
|
76
|
+
prompts_dir = defaults.get('prompts_dir', '')
|
|
77
|
+
if prompts_dir:
|
|
78
|
+
normalized = prompts_dir.rstrip('/')
|
|
79
|
+
prefix = normalized
|
|
80
|
+
if normalized == 'prompts':
|
|
81
|
+
prefix = ''
|
|
82
|
+
elif normalized.startswith('prompts/'):
|
|
83
|
+
prefix = normalized[len('prompts/'):]
|
|
84
|
+
|
|
85
|
+
if prefix and (basename == prefix or basename.startswith(prefix + '/')):
|
|
86
|
+
relative = basename[len(prefix) + 1 :] if basename != prefix else basename.split('/')[-1]
|
|
87
|
+
matches.append((len(prefix), relative))
|
|
88
|
+
|
|
89
|
+
if not matches:
|
|
90
|
+
return basename
|
|
91
|
+
|
|
92
|
+
matches.sort(key=lambda item: item[0], reverse=True)
|
|
93
|
+
return matches[0][1]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _normalize_prompts_root(prompts_dir: Path) -> Path:
|
|
97
|
+
"""
|
|
98
|
+
Resolve prompts_dir to an absolute path relative to the project root.
|
|
99
|
+
|
|
100
|
+
This function takes a potentially relative prompts_dir path (e.g., "prompts/backend")
|
|
101
|
+
and resolves it to an absolute path using the .pddrc location as the project root.
|
|
102
|
+
|
|
103
|
+
Note: This function previously stripped subdirectories after "prompts" which was
|
|
104
|
+
incorrect for context-specific prompts_dir values. Fixed in Issue #253.
|
|
105
|
+
"""
|
|
106
|
+
prompts_root = Path(prompts_dir)
|
|
107
|
+
pddrc_path = _find_pddrc_file()
|
|
108
|
+
if pddrc_path and not prompts_root.is_absolute():
|
|
109
|
+
prompts_root = pddrc_path.parent / prompts_root
|
|
110
|
+
|
|
111
|
+
return prompts_root
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _find_prompt_in_contexts(basename: str) -> Optional[Tuple[str, Path, str]]:
|
|
115
|
+
"""
|
|
116
|
+
Search for a prompt file across all contexts using outputs.prompt.path templates.
|
|
117
|
+
|
|
118
|
+
This enables finding prompts when the basename alone doesn't match context path patterns.
|
|
119
|
+
For example, 'credit_helpers' can find 'prompts/backend/utils/credit_helpers_python.prompt'
|
|
120
|
+
if the backend-utils context has outputs.prompt.path configured.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
basename: The base name for the prompt file (e.g., 'credit_helpers')
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Tuple of (context_name, prompt_path, language) if found, None otherwise
|
|
127
|
+
"""
|
|
128
|
+
pddrc_path = _find_pddrc_file()
|
|
129
|
+
if not pddrc_path:
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
config = _load_pddrc_config(pddrc_path)
|
|
134
|
+
except Exception:
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
# Resolve paths relative to .pddrc location, not CWD
|
|
138
|
+
pddrc_parent = pddrc_path.parent
|
|
139
|
+
|
|
140
|
+
contexts = config.get('contexts', {})
|
|
141
|
+
|
|
142
|
+
# Common languages to try
|
|
143
|
+
languages_to_try = ['python', 'typescript', 'javascript', 'typescriptreact', 'go', 'rust', 'java']
|
|
144
|
+
|
|
145
|
+
for context_name, context_config in contexts.items():
|
|
146
|
+
if context_name == 'default':
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
defaults = context_config.get('defaults', {})
|
|
150
|
+
outputs = defaults.get('outputs', {})
|
|
151
|
+
prompt_config = outputs.get('prompt', {})
|
|
152
|
+
prompt_template = prompt_config.get('path')
|
|
153
|
+
|
|
154
|
+
if not prompt_template:
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
context_basename = _relative_basename_for_context(basename, context_config)
|
|
158
|
+
parts = context_basename.split('/') if context_basename else ['']
|
|
159
|
+
name_part = parts[-1]
|
|
160
|
+
category = '/'.join(parts[:-1]) if len(parts) > 1 else ''
|
|
161
|
+
dir_prefix = f"{category}/" if category else ''
|
|
162
|
+
|
|
163
|
+
# Try each language
|
|
164
|
+
for lang in languages_to_try:
|
|
165
|
+
ext = _get_extension_safe(lang)
|
|
166
|
+
template_context = {
|
|
167
|
+
'name': name_part,
|
|
168
|
+
'category': category,
|
|
169
|
+
'dir_prefix': dir_prefix,
|
|
170
|
+
'ext': ext,
|
|
171
|
+
'language': lang,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
expanded_path = expand_template(prompt_template, template_context)
|
|
175
|
+
# Resolve relative to .pddrc location, not CWD
|
|
176
|
+
prompt_path = pddrc_parent / expanded_path
|
|
177
|
+
|
|
178
|
+
if prompt_path.exists():
|
|
179
|
+
return (context_name, prompt_path, lang)
|
|
180
|
+
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _detect_languages_with_context(basename: str, prompts_dir: Path, context_name: Optional[str] = None) -> List[str]:
|
|
185
|
+
"""
|
|
186
|
+
Detects all available languages for a given basename, optionally using context config.
|
|
187
|
+
|
|
188
|
+
If context_name is provided and has outputs.prompt.path configured, uses template-based
|
|
189
|
+
discovery. Otherwise falls back to directory scanning.
|
|
190
|
+
"""
|
|
191
|
+
if context_name:
|
|
192
|
+
pddrc_path = _find_pddrc_file()
|
|
193
|
+
if pddrc_path:
|
|
194
|
+
try:
|
|
195
|
+
config = _load_pddrc_config(pddrc_path)
|
|
196
|
+
# Resolve paths relative to .pddrc location, not CWD
|
|
197
|
+
pddrc_parent = pddrc_path.parent
|
|
198
|
+
contexts = config.get('contexts', {})
|
|
199
|
+
context_config = contexts.get(context_name, {})
|
|
200
|
+
defaults = context_config.get('defaults', {})
|
|
201
|
+
outputs = defaults.get('outputs', {})
|
|
202
|
+
prompt_config = outputs.get('prompt', {})
|
|
203
|
+
prompt_template = prompt_config.get('path')
|
|
204
|
+
|
|
205
|
+
if prompt_template:
|
|
206
|
+
context_basename = _relative_basename_for_context(basename, context_config)
|
|
207
|
+
parts = context_basename.split('/') if context_basename else ['']
|
|
208
|
+
name_part = parts[-1]
|
|
209
|
+
category = '/'.join(parts[:-1]) if len(parts) > 1 else ''
|
|
210
|
+
dir_prefix = f"{category}/" if category else ''
|
|
211
|
+
|
|
212
|
+
# Try all known languages
|
|
213
|
+
languages_to_try = ['python', 'typescript', 'javascript', 'typescriptreact', 'go', 'rust', 'java']
|
|
214
|
+
found_languages = []
|
|
215
|
+
|
|
216
|
+
for lang in languages_to_try:
|
|
217
|
+
ext = _get_extension_safe(lang)
|
|
218
|
+
template_context = {
|
|
219
|
+
'name': name_part,
|
|
220
|
+
'category': category,
|
|
221
|
+
'dir_prefix': dir_prefix,
|
|
222
|
+
'ext': ext,
|
|
223
|
+
'language': lang,
|
|
224
|
+
}
|
|
225
|
+
expanded_path = expand_template(prompt_template, template_context)
|
|
226
|
+
# Resolve relative to .pddrc location, not CWD
|
|
227
|
+
if (pddrc_parent / expanded_path).exists():
|
|
228
|
+
found_languages.append(lang)
|
|
229
|
+
|
|
230
|
+
if found_languages:
|
|
231
|
+
# Return with Python first if present
|
|
232
|
+
if 'python' in found_languages:
|
|
233
|
+
other = sorted([l for l in found_languages if l != 'python'])
|
|
234
|
+
return ['python'] + other
|
|
235
|
+
return sorted(found_languages)
|
|
236
|
+
except Exception:
|
|
237
|
+
pass
|
|
238
|
+
|
|
239
|
+
# Fallback to original directory scanning
|
|
240
|
+
return _detect_languages(basename, prompts_dir)
|
|
241
|
+
|
|
242
|
+
|
|
39
243
|
def _detect_languages(basename: str, prompts_dir: Path) -> List[str]:
|
|
40
244
|
"""
|
|
41
245
|
Detects all available languages for a given basename by finding
|
|
42
246
|
matching prompt files in the prompts directory.
|
|
43
247
|
Excludes runtime languages (LLM) as they cannot form valid development units.
|
|
248
|
+
|
|
249
|
+
Supports subdirectory basenames like 'core/cloud':
|
|
250
|
+
- For basename 'core/cloud', searches in prompts/core/ for cloud_*.prompt files
|
|
251
|
+
- The stem comparison only uses the filename part ('cloud'), not the path ('core/cloud')
|
|
44
252
|
"""
|
|
45
253
|
development_languages = []
|
|
46
254
|
if not prompts_dir.is_dir():
|
|
47
255
|
return []
|
|
48
256
|
|
|
257
|
+
# For subdirectory basenames, extract just the name part for stem comparison
|
|
258
|
+
if '/' in basename:
|
|
259
|
+
name_part = basename.rsplit('/', 1)[1] # 'cloud' from 'core/cloud'
|
|
260
|
+
else:
|
|
261
|
+
name_part = basename
|
|
262
|
+
|
|
49
263
|
pattern = f"{basename}_*.prompt"
|
|
50
264
|
for prompt_file in prompts_dir.glob(pattern):
|
|
51
|
-
# stem is '
|
|
265
|
+
# stem is the filename without extension (e.g., 'cloud_python')
|
|
52
266
|
stem = prompt_file.stem
|
|
53
|
-
# Ensure the file starts with the exact
|
|
54
|
-
if stem.startswith(f"{
|
|
55
|
-
potential_language = stem[len(
|
|
267
|
+
# Ensure the file starts with the exact name part followed by an underscore
|
|
268
|
+
if stem.startswith(f"{name_part}_"):
|
|
269
|
+
potential_language = stem[len(name_part) + 1 :]
|
|
56
270
|
try:
|
|
57
271
|
if _is_known_language(potential_language):
|
|
58
272
|
# Exclude runtime languages (LLM) as they cannot form valid development units
|
|
@@ -118,9 +332,10 @@ def sync_main(
|
|
|
118
332
|
local = ctx.obj.get("local", False)
|
|
119
333
|
context_override = ctx.obj.get("context", None)
|
|
120
334
|
|
|
121
|
-
# Default values for max_attempts
|
|
335
|
+
# Default values for max_attempts, budget, target_coverage when not specified via CLI or .pddrc
|
|
122
336
|
DEFAULT_MAX_ATTEMPTS = 3
|
|
123
337
|
DEFAULT_BUDGET = 20.0
|
|
338
|
+
DEFAULT_TARGET_COVERAGE = 90.0
|
|
124
339
|
|
|
125
340
|
# 2. Validate inputs (basename only - budget/max_attempts validated after config resolution)
|
|
126
341
|
_validate_basename(basename)
|
|
@@ -132,23 +347,47 @@ def sync_main(
|
|
|
132
347
|
if max_attempts is not None and max_attempts < 0:
|
|
133
348
|
raise click.BadParameter("Max attempts must be a non-negative integer.", param_hint="--max-attempts")
|
|
134
349
|
|
|
135
|
-
# 3.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
350
|
+
# 3. Try template-based prompt discovery first (uses outputs.prompt.path from .pddrc)
|
|
351
|
+
template_result = _find_prompt_in_contexts(basename)
|
|
352
|
+
discovered_context = None
|
|
353
|
+
|
|
354
|
+
if template_result:
|
|
355
|
+
discovered_context, discovered_prompt_path, first_lang = template_result
|
|
356
|
+
prompts_dir_raw = discovered_prompt_path.parent
|
|
357
|
+
pddrc_path = _find_pddrc_file()
|
|
358
|
+
if pddrc_path and not prompts_dir_raw.is_absolute():
|
|
359
|
+
prompts_dir = pddrc_path.parent / prompts_dir_raw
|
|
360
|
+
else:
|
|
361
|
+
prompts_dir = prompts_dir_raw
|
|
362
|
+
# Use context override if not already set
|
|
363
|
+
if not context_override:
|
|
364
|
+
context_override = discovered_context
|
|
365
|
+
if not quiet:
|
|
366
|
+
rprint(f"[dim]Found prompt via template in context: {discovered_context}[/dim]")
|
|
367
|
+
|
|
368
|
+
# 4. Fallback: Use construct_paths in 'discovery' mode to find the prompts directory.
|
|
369
|
+
if not template_result:
|
|
370
|
+
try:
|
|
371
|
+
initial_config, _, _, _ = construct_paths(
|
|
372
|
+
input_file_paths={},
|
|
373
|
+
force=False,
|
|
374
|
+
quiet=True,
|
|
375
|
+
command="sync",
|
|
376
|
+
command_options={"basename": basename},
|
|
377
|
+
context_override=context_override,
|
|
378
|
+
)
|
|
379
|
+
prompts_dir_raw = initial_config.get("prompts_dir", "prompts")
|
|
380
|
+
pddrc_path = _find_pddrc_file()
|
|
381
|
+
if pddrc_path and not Path(prompts_dir_raw).is_absolute():
|
|
382
|
+
prompts_dir = pddrc_path.parent / prompts_dir_raw
|
|
383
|
+
else:
|
|
384
|
+
prompts_dir = Path(prompts_dir_raw)
|
|
385
|
+
except Exception as e:
|
|
386
|
+
rprint(f"[bold red]Error initializing PDD paths:[/bold red] {e}")
|
|
387
|
+
raise click.Abort()
|
|
149
388
|
|
|
150
|
-
#
|
|
151
|
-
languages =
|
|
389
|
+
# 5. Detect all languages for the given basename
|
|
390
|
+
languages = _detect_languages_with_context(basename, prompts_dir, context_name=discovered_context)
|
|
152
391
|
if not languages:
|
|
153
392
|
raise click.UsageError(
|
|
154
393
|
f"No prompt files found for basename '{basename}' in directory '{prompts_dir}'.\n"
|
|
@@ -274,10 +513,15 @@ def sync_main(
|
|
|
274
513
|
# Priority: CLI value > .pddrc value > hardcoded default
|
|
275
514
|
final_strength = resolved_config.get("strength", strength)
|
|
276
515
|
final_temp = resolved_config.get("temperature", temperature)
|
|
277
|
-
final_target_coverage = resolved_config.get("target_coverage", target_coverage)
|
|
278
516
|
|
|
279
|
-
# For max_attempts and budget: CLI > .pddrc > hardcoded default
|
|
517
|
+
# For target_coverage, max_attempts and budget: CLI > .pddrc > hardcoded default
|
|
280
518
|
# If CLI value is provided (not None), use it. Otherwise, use .pddrc or default.
|
|
519
|
+
# Issue #194: target_coverage was not being handled consistently with the others
|
|
520
|
+
if target_coverage is not None:
|
|
521
|
+
final_target_coverage = target_coverage
|
|
522
|
+
else:
|
|
523
|
+
final_target_coverage = resolved_config.get("target_coverage") or DEFAULT_TARGET_COVERAGE
|
|
524
|
+
|
|
281
525
|
if max_attempts is not None:
|
|
282
526
|
final_max_attempts = max_attempts
|
|
283
527
|
else:
|