pdd-cli 0.0.45__py3-none-any.whl → 0.0.118__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 +40 -8
- pdd/agentic_bug.py +323 -0
- pdd/agentic_bug_orchestrator.py +497 -0
- pdd/agentic_change.py +231 -0
- pdd/agentic_change_orchestrator.py +526 -0
- pdd/agentic_common.py +598 -0
- pdd/agentic_crash.py +534 -0
- pdd/agentic_e2e_fix.py +319 -0
- pdd/agentic_e2e_fix_orchestrator.py +426 -0
- pdd/agentic_fix.py +1294 -0
- pdd/agentic_langtest.py +162 -0
- pdd/agentic_update.py +387 -0
- pdd/agentic_verify.py +183 -0
- pdd/architecture_sync.py +565 -0
- pdd/auth_service.py +210 -0
- pdd/auto_deps_main.py +71 -51
- pdd/auto_include.py +245 -5
- pdd/auto_update.py +125 -47
- pdd/bug_main.py +196 -23
- pdd/bug_to_unit_test.py +2 -0
- pdd/change_main.py +11 -4
- pdd/cli.py +22 -1181
- pdd/cmd_test_main.py +350 -150
- pdd/code_generator.py +60 -18
- pdd/code_generator_main.py +790 -57
- pdd/commands/__init__.py +48 -0
- pdd/commands/analysis.py +306 -0
- pdd/commands/auth.py +309 -0
- pdd/commands/connect.py +290 -0
- pdd/commands/fix.py +163 -0
- pdd/commands/generate.py +257 -0
- pdd/commands/maintenance.py +175 -0
- pdd/commands/misc.py +87 -0
- pdd/commands/modify.py +256 -0
- pdd/commands/report.py +144 -0
- pdd/commands/sessions.py +284 -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 +589 -111
- pdd/context_generator.py +10 -2
- pdd/context_generator_main.py +175 -76
- pdd/continue_generation.py +53 -10
- pdd/core/__init__.py +33 -0
- pdd/core/cli.py +527 -0
- pdd/core/cloud.py +237 -0
- pdd/core/dump.py +554 -0
- pdd/core/errors.py +67 -0
- pdd/core/remote_session.py +61 -0
- pdd/core/utils.py +90 -0
- pdd/crash_main.py +262 -33
- pdd/data/language_format.csv +71 -63
- pdd/data/llm_model.csv +20 -18
- pdd/detect_change_main.py +5 -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 +523 -95
- pdd/fix_code_module_errors.py +6 -2
- pdd/fix_error_loop.py +491 -92
- pdd/fix_errors_from_unit_tests.py +4 -3
- pdd/fix_main.py +278 -21
- pdd/fix_verification_errors.py +12 -100
- pdd/fix_verification_errors_loop.py +529 -286
- pdd/fix_verification_main.py +294 -89
- pdd/frontend/dist/assets/index-B5DZHykP.css +1 -0
- pdd/frontend/dist/assets/index-DQ3wkeQ2.js +449 -0
- pdd/frontend/dist/index.html +376 -0
- pdd/frontend/dist/logo.svg +33 -0
- pdd/generate_output_paths.py +139 -15
- pdd/generate_test.py +218 -146
- pdd/get_comment.py +19 -44
- pdd/get_extension.py +8 -9
- pdd/get_jwt_token.py +318 -22
- pdd/get_language.py +8 -7
- pdd/get_run_command.py +75 -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 +13 -4
- pdd/llm_invoke.py +1711 -181
- pdd/load_prompt_template.py +19 -12
- pdd/path_resolution.py +140 -0
- pdd/pdd_completion.fish +25 -2
- pdd/pdd_completion.sh +30 -4
- pdd/pdd_completion.zsh +79 -4
- pdd/postprocess.py +14 -4
- pdd/preprocess.py +293 -24
- pdd/preprocess_main.py +41 -6
- 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 +131 -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_crash_explore_LLM.prompt +49 -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_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 +925 -0
- pdd/prompts/agentic_verify_explore_LLM.prompt +45 -0
- pdd/prompts/auto_include_LLM.prompt +122 -905
- pdd/prompts/change_LLM.prompt +3093 -1
- pdd/prompts/detect_change_LLM.prompt +686 -27
- 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 +12 -2
- pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +9 -0
- pdd/prompts/fix_verification_errors_LLM.prompt +22 -0
- pdd/prompts/generate_test_LLM.prompt +41 -7
- pdd/prompts/generate_test_from_example_LLM.prompt +115 -0
- pdd/prompts/increase_tests_LLM.prompt +1 -5
- pdd/prompts/insert_includes_LLM.prompt +316 -186
- pdd/prompts/prompt_code_diff_LLM.prompt +119 -0
- pdd/prompts/prompt_diff_LLM.prompt +82 -0
- 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/remote_session.py +876 -0
- pdd/render_mermaid.py +236 -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 +1322 -0
- pdd/server/routes/websocket.py +473 -0
- pdd/server/security.py +243 -0
- pdd/server/terminal_spawner.py +209 -0
- pdd/server/token_counter.py +222 -0
- pdd/setup_tool.py +648 -0
- pdd/simple_math.py +2 -0
- pdd/split_main.py +3 -2
- pdd/summarize_directory.py +237 -195
- pdd/sync_animation.py +8 -4
- pdd/sync_determine_operation.py +839 -112
- pdd/sync_main.py +351 -57
- pdd/sync_orchestration.py +1400 -756
- pdd/sync_tui.py +848 -0
- pdd/template_expander.py +161 -0
- pdd/template_registry.py +264 -0
- pdd/templates/architecture/architecture_json.prompt +237 -0
- pdd/templates/generic/generate_prompt.prompt +174 -0
- pdd/trace.py +168 -12
- pdd/trace_main.py +4 -3
- pdd/track_cost.py +140 -63
- pdd/unfinished_prompt.py +51 -4
- pdd/update_main.py +567 -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.118.dist-info}/METADATA +29 -11
- pdd_cli-0.0.118.dist-info/RECORD +227 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.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.118.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Prompt Driven Development</title>
|
|
8
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
9
|
+
<script>
|
|
10
|
+
tailwind.config = {
|
|
11
|
+
theme: {
|
|
12
|
+
screens: {
|
|
13
|
+
'xs': '475px',
|
|
14
|
+
'sm': '640px',
|
|
15
|
+
'md': '768px',
|
|
16
|
+
'lg': '1024px',
|
|
17
|
+
'xl': '1280px',
|
|
18
|
+
'2xl': '1536px',
|
|
19
|
+
},
|
|
20
|
+
extend: {
|
|
21
|
+
colors: {
|
|
22
|
+
surface: {
|
|
23
|
+
50: '#f8fafc',
|
|
24
|
+
100: '#f1f5f9',
|
|
25
|
+
200: '#e2e8f0',
|
|
26
|
+
300: '#cbd5e1',
|
|
27
|
+
400: '#94a3b8',
|
|
28
|
+
500: '#64748b',
|
|
29
|
+
600: '#475569',
|
|
30
|
+
700: '#334155',
|
|
31
|
+
800: '#1e293b',
|
|
32
|
+
900: '#0f172a',
|
|
33
|
+
950: '#020617',
|
|
34
|
+
},
|
|
35
|
+
accent: {
|
|
36
|
+
50: '#eff6ff',
|
|
37
|
+
100: '#dbeafe',
|
|
38
|
+
200: '#bfdbfe',
|
|
39
|
+
300: '#93c5fd',
|
|
40
|
+
400: '#60a5fa',
|
|
41
|
+
500: '#3b82f6',
|
|
42
|
+
600: '#2563eb',
|
|
43
|
+
700: '#1d4ed8',
|
|
44
|
+
800: '#1e40af',
|
|
45
|
+
900: '#1e3a8a',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
fontFamily: {
|
|
49
|
+
sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
|
|
50
|
+
mono: ['JetBrains Mono', 'Fira Code', 'ui-monospace', 'monospace'],
|
|
51
|
+
},
|
|
52
|
+
boxShadow: {
|
|
53
|
+
'glow': '0 0 20px rgba(59, 130, 246, 0.3)',
|
|
54
|
+
'glow-lg': '0 0 40px rgba(59, 130, 246, 0.4)',
|
|
55
|
+
'inner-glow': 'inset 0 1px 0 rgba(255, 255, 255, 0.05)',
|
|
56
|
+
},
|
|
57
|
+
backdropBlur: {
|
|
58
|
+
xs: '2px',
|
|
59
|
+
},
|
|
60
|
+
animation: {
|
|
61
|
+
'fade-in': 'fadeIn 0.3s ease-out',
|
|
62
|
+
'slide-up': 'slideUp 0.3s ease-out',
|
|
63
|
+
'slide-down': 'slideDown 0.2s ease-out',
|
|
64
|
+
'scale-in': 'scaleIn 0.2s ease-out',
|
|
65
|
+
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
|
66
|
+
'slide-in-right': 'slideInRight 0.25s ease-out',
|
|
67
|
+
},
|
|
68
|
+
textColor: {
|
|
69
|
+
text: {
|
|
70
|
+
primary: '#f1f5f9',
|
|
71
|
+
secondary: '#94a3b8',
|
|
72
|
+
muted: '#64748b',
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
</script>
|
|
79
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
80
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
81
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
82
|
+
<style>
|
|
83
|
+
* {
|
|
84
|
+
-webkit-font-smoothing: antialiased;
|
|
85
|
+
-moz-osx-font-smoothing: grayscale;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
body {
|
|
89
|
+
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Custom scrollbar - Webkit (Chrome, Safari, Edge) */
|
|
93
|
+
::-webkit-scrollbar {
|
|
94
|
+
width: 10px;
|
|
95
|
+
height: 10px;
|
|
96
|
+
}
|
|
97
|
+
::-webkit-scrollbar-track {
|
|
98
|
+
background: #1e293b;
|
|
99
|
+
border-radius: 5px;
|
|
100
|
+
}
|
|
101
|
+
::-webkit-scrollbar-thumb {
|
|
102
|
+
background: #475569;
|
|
103
|
+
border-radius: 5px;
|
|
104
|
+
border: 2px solid #1e293b;
|
|
105
|
+
}
|
|
106
|
+
::-webkit-scrollbar-thumb:hover {
|
|
107
|
+
background: #64748b;
|
|
108
|
+
}
|
|
109
|
+
::-webkit-scrollbar-corner {
|
|
110
|
+
background: #1e293b;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* Firefox scrollbar */
|
|
114
|
+
* {
|
|
115
|
+
scrollbar-width: thin;
|
|
116
|
+
scrollbar-color: #475569 #1e293b;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* Animations */
|
|
120
|
+
@keyframes fadeIn {
|
|
121
|
+
from { opacity: 0; transform: translateY(-10px); }
|
|
122
|
+
to { opacity: 1; transform: translateY(0); }
|
|
123
|
+
}
|
|
124
|
+
@keyframes slideUp {
|
|
125
|
+
from { opacity: 0; transform: translateY(20px); }
|
|
126
|
+
to { opacity: 1; transform: translateY(0); }
|
|
127
|
+
}
|
|
128
|
+
@keyframes slideDown {
|
|
129
|
+
from { opacity: 0; transform: translateY(-10px); }
|
|
130
|
+
to { opacity: 1; transform: translateY(0); }
|
|
131
|
+
}
|
|
132
|
+
@keyframes scaleIn {
|
|
133
|
+
from { opacity: 0; transform: scale(0.95); }
|
|
134
|
+
to { opacity: 1; transform: scale(1); }
|
|
135
|
+
}
|
|
136
|
+
@keyframes slideInRight {
|
|
137
|
+
from { opacity: 0; transform: translateX(20px); }
|
|
138
|
+
to { opacity: 1; transform: translateX(0); }
|
|
139
|
+
}
|
|
140
|
+
@keyframes indeterminate {
|
|
141
|
+
0% { transform: translateX(-100%); }
|
|
142
|
+
100% { transform: translateX(300%); }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.animate-fade-in { animation: fadeIn 0.3s ease-out; }
|
|
146
|
+
.animate-indeterminate { animation: indeterminate 1.5s ease-in-out infinite; }
|
|
147
|
+
.animate-slide-up { animation: slideUp 0.3s ease-out; }
|
|
148
|
+
.animate-slide-down { animation: slideDown 0.2s ease-out; }
|
|
149
|
+
.animate-scale-in { animation: scaleIn 0.2s ease-out; }
|
|
150
|
+
.animate-slide-in-right { animation: slideInRight 0.25s ease-out; }
|
|
151
|
+
|
|
152
|
+
/* Glass morphism utility */
|
|
153
|
+
.glass {
|
|
154
|
+
background: rgba(30, 41, 59, 0.7);
|
|
155
|
+
backdrop-filter: blur(12px);
|
|
156
|
+
-webkit-backdrop-filter: blur(12px);
|
|
157
|
+
}
|
|
158
|
+
.glass-light {
|
|
159
|
+
background: rgba(51, 65, 85, 0.5);
|
|
160
|
+
backdrop-filter: blur(8px);
|
|
161
|
+
-webkit-backdrop-filter: blur(8px);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* Gradient text utility */
|
|
165
|
+
.text-gradient {
|
|
166
|
+
background: linear-gradient(135deg, #60a5fa, #a78bfa);
|
|
167
|
+
-webkit-background-clip: text;
|
|
168
|
+
-webkit-text-fill-color: transparent;
|
|
169
|
+
background-clip: text;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* Focus ring */
|
|
173
|
+
.focus-ring:focus {
|
|
174
|
+
outline: none;
|
|
175
|
+
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* Card hover effect */
|
|
179
|
+
.card-hover {
|
|
180
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
181
|
+
}
|
|
182
|
+
.card-hover:hover {
|
|
183
|
+
transform: translateY(-2px);
|
|
184
|
+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* Markdown rendering styles */
|
|
188
|
+
.markdown-body {
|
|
189
|
+
color: #e2e8f0;
|
|
190
|
+
line-height: 1.7;
|
|
191
|
+
font-size: 15px;
|
|
192
|
+
}
|
|
193
|
+
.markdown-body h1, .markdown-body h2, .markdown-body h3,
|
|
194
|
+
.markdown-body h4, .markdown-body h5, .markdown-body h6 {
|
|
195
|
+
color: #f1f5f9;
|
|
196
|
+
font-weight: 600;
|
|
197
|
+
margin-top: 1.5em;
|
|
198
|
+
margin-bottom: 0.75em;
|
|
199
|
+
line-height: 1.3;
|
|
200
|
+
}
|
|
201
|
+
.markdown-body h1 { font-size: 2em; border-bottom: 1px solid #334155; padding-bottom: 0.3em; }
|
|
202
|
+
.markdown-body h2 { font-size: 1.5em; border-bottom: 1px solid #334155; padding-bottom: 0.3em; }
|
|
203
|
+
.markdown-body h3 { font-size: 1.25em; }
|
|
204
|
+
.markdown-body h4 { font-size: 1.1em; }
|
|
205
|
+
.markdown-body p { margin-bottom: 1em; }
|
|
206
|
+
.markdown-body ul, .markdown-body ol {
|
|
207
|
+
padding-left: 1.5em;
|
|
208
|
+
margin-bottom: 1em;
|
|
209
|
+
}
|
|
210
|
+
.markdown-body li { margin-bottom: 0.35em; }
|
|
211
|
+
.markdown-body ul { list-style-type: disc; }
|
|
212
|
+
.markdown-body ol { list-style-type: decimal; }
|
|
213
|
+
.markdown-body li > ul, .markdown-body li > ol { margin-top: 0.35em; margin-bottom: 0; }
|
|
214
|
+
.markdown-body code {
|
|
215
|
+
background: #1e293b;
|
|
216
|
+
padding: 0.2em 0.4em;
|
|
217
|
+
border-radius: 6px;
|
|
218
|
+
font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace;
|
|
219
|
+
font-size: 0.9em;
|
|
220
|
+
color: #93c5fd;
|
|
221
|
+
}
|
|
222
|
+
.markdown-body pre {
|
|
223
|
+
background: #0f172a;
|
|
224
|
+
border: 1px solid #334155;
|
|
225
|
+
border-radius: 8px;
|
|
226
|
+
padding: 1em;
|
|
227
|
+
overflow-x: auto;
|
|
228
|
+
margin-bottom: 1em;
|
|
229
|
+
}
|
|
230
|
+
.markdown-body pre code {
|
|
231
|
+
background: transparent;
|
|
232
|
+
padding: 0;
|
|
233
|
+
border-radius: 0;
|
|
234
|
+
color: #e2e8f0;
|
|
235
|
+
font-size: 0.875em;
|
|
236
|
+
}
|
|
237
|
+
.markdown-body blockquote {
|
|
238
|
+
border-left: 4px solid #3b82f6;
|
|
239
|
+
padding-left: 1em;
|
|
240
|
+
margin-left: 0;
|
|
241
|
+
margin-bottom: 1em;
|
|
242
|
+
color: #94a3b8;
|
|
243
|
+
font-style: italic;
|
|
244
|
+
}
|
|
245
|
+
.markdown-body a {
|
|
246
|
+
color: #60a5fa;
|
|
247
|
+
text-decoration: none;
|
|
248
|
+
}
|
|
249
|
+
.markdown-body a:hover { text-decoration: underline; }
|
|
250
|
+
.markdown-body hr {
|
|
251
|
+
border: none;
|
|
252
|
+
border-top: 1px solid #334155;
|
|
253
|
+
margin: 2em 0;
|
|
254
|
+
}
|
|
255
|
+
.markdown-body table {
|
|
256
|
+
width: 100%;
|
|
257
|
+
border-collapse: collapse;
|
|
258
|
+
margin-bottom: 1em;
|
|
259
|
+
}
|
|
260
|
+
.markdown-body th, .markdown-body td {
|
|
261
|
+
border: 1px solid #334155;
|
|
262
|
+
padding: 0.5em 0.75em;
|
|
263
|
+
text-align: left;
|
|
264
|
+
}
|
|
265
|
+
.markdown-body th {
|
|
266
|
+
background: #1e293b;
|
|
267
|
+
font-weight: 600;
|
|
268
|
+
}
|
|
269
|
+
.markdown-body img {
|
|
270
|
+
max-width: 100%;
|
|
271
|
+
border-radius: 8px;
|
|
272
|
+
}
|
|
273
|
+
.markdown-body strong { color: #f1f5f9; font-weight: 600; }
|
|
274
|
+
.markdown-body em { color: #cbd5e1; }
|
|
275
|
+
|
|
276
|
+
/* ReactFlow edge selection styles */
|
|
277
|
+
.react-flow__edge.selected .react-flow__edge-path {
|
|
278
|
+
stroke: #f97316 !important;
|
|
279
|
+
stroke-width: 3px !important;
|
|
280
|
+
}
|
|
281
|
+
.react-flow__edge:hover .react-flow__edge-path {
|
|
282
|
+
stroke: #60a5fa !important;
|
|
283
|
+
stroke-width: 2.5px !important;
|
|
284
|
+
}
|
|
285
|
+
.react-flow__edge.selected:hover .react-flow__edge-path {
|
|
286
|
+
stroke: #fb923c !important;
|
|
287
|
+
stroke-width: 3px !important;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/* CodeMirror Autocomplete styles */
|
|
291
|
+
.cm-tooltip {
|
|
292
|
+
z-index: 1000 !important;
|
|
293
|
+
}
|
|
294
|
+
.cm-tooltip.cm-tooltip-autocomplete {
|
|
295
|
+
background: #1e293b !important;
|
|
296
|
+
border: 1px solid #334155 !important;
|
|
297
|
+
border-radius: 8px !important;
|
|
298
|
+
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.4) !important;
|
|
299
|
+
overflow: hidden !important;
|
|
300
|
+
}
|
|
301
|
+
.cm-tooltip.cm-tooltip-autocomplete > ul {
|
|
302
|
+
font-family: inherit !important;
|
|
303
|
+
max-height: 280px !important;
|
|
304
|
+
padding: 4px 0 !important;
|
|
305
|
+
}
|
|
306
|
+
.cm-tooltip.cm-tooltip-autocomplete > ul > li {
|
|
307
|
+
padding: 8px 12px !important;
|
|
308
|
+
display: flex !important;
|
|
309
|
+
align-items: center !important;
|
|
310
|
+
gap: 8px !important;
|
|
311
|
+
cursor: pointer !important;
|
|
312
|
+
}
|
|
313
|
+
.cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected] {
|
|
314
|
+
background: #3b82f6 !important;
|
|
315
|
+
color: white !important;
|
|
316
|
+
}
|
|
317
|
+
.cm-completionLabel {
|
|
318
|
+
color: #60a5fa !important;
|
|
319
|
+
font-weight: 500 !important;
|
|
320
|
+
}
|
|
321
|
+
.cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected] .cm-completionLabel {
|
|
322
|
+
color: white !important;
|
|
323
|
+
}
|
|
324
|
+
.cm-completionDetail {
|
|
325
|
+
color: #94a3b8 !important;
|
|
326
|
+
font-size: 11px !important;
|
|
327
|
+
margin-left: auto !important;
|
|
328
|
+
font-style: normal !important;
|
|
329
|
+
}
|
|
330
|
+
.cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected] .cm-completionDetail {
|
|
331
|
+
color: rgba(255, 255, 255, 0.8) !important;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/* Mobile responsive utilities */
|
|
335
|
+
/* Hide scrollbars utility */
|
|
336
|
+
.scrollbar-hide {
|
|
337
|
+
-ms-overflow-style: none;
|
|
338
|
+
scrollbar-width: none;
|
|
339
|
+
}
|
|
340
|
+
.scrollbar-hide::-webkit-scrollbar {
|
|
341
|
+
display: none;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/* Safe area insets for notched devices */
|
|
345
|
+
.safe-top {
|
|
346
|
+
padding-top: env(safe-area-inset-top);
|
|
347
|
+
}
|
|
348
|
+
.safe-bottom {
|
|
349
|
+
padding-bottom: env(safe-area-inset-bottom);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/* Prevent iOS zoom on input focus */
|
|
353
|
+
@media screen and (max-width: 640px) {
|
|
354
|
+
input, textarea, select {
|
|
355
|
+
font-size: 16px !important;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
</style>
|
|
359
|
+
|
|
360
|
+
<script type="importmap">
|
|
361
|
+
{
|
|
362
|
+
"imports": {
|
|
363
|
+
"react-dom/": "https://aistudiocdn.com/react-dom@^19.2.0/",
|
|
364
|
+
"react/": "https://aistudiocdn.com/react@^19.2.0/",
|
|
365
|
+
"react": "https://aistudiocdn.com/react@^19.2.0",
|
|
366
|
+
"@google/genai": "https://aistudiocdn.com/@google/genai@^0.15.0"
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
</script>
|
|
370
|
+
<script type="module" crossorigin src="/assets/index-DQ3wkeQ2.js"></script>
|
|
371
|
+
<link rel="stylesheet" crossorigin href="/assets/index-B5DZHykP.css">
|
|
372
|
+
</head>
|
|
373
|
+
<body class="bg-surface-950 text-gray-200">
|
|
374
|
+
<div id="root"></div>
|
|
375
|
+
</body>
|
|
376
|
+
</html>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
|
3
|
+
<defs>
|
|
4
|
+
<filter id="glow" x="-60%" y="-60%" width="220%" height="220%">
|
|
5
|
+
<feGaussianBlur in="SourceGraphic" stdDeviation="40" result="blur"/>
|
|
6
|
+
<feMerge>
|
|
7
|
+
<feMergeNode in="blur"/>
|
|
8
|
+
<feMergeNode in="SourceGraphic"/>
|
|
9
|
+
</feMerge>
|
|
10
|
+
</filter>
|
|
11
|
+
</defs>
|
|
12
|
+
|
|
13
|
+
<g stroke="#00e3ff"
|
|
14
|
+
stroke-width="60"
|
|
15
|
+
stroke-linecap="round"
|
|
16
|
+
stroke-linejoin="round"
|
|
17
|
+
fill="none"
|
|
18
|
+
filter="url(#glow)">
|
|
19
|
+
|
|
20
|
+
<!-- speech-bubble "P" outline -->
|
|
21
|
+
<path d="
|
|
22
|
+
M 260 180
|
|
23
|
+
H 600
|
|
24
|
+
A 230 230 0 0 1 600 660
|
|
25
|
+
H 480
|
|
26
|
+
L 260 880
|
|
27
|
+
V 180
|
|
28
|
+
Z"/>
|
|
29
|
+
|
|
30
|
+
<!-- chevron shifted +15 px -->
|
|
31
|
+
<polyline points="505 340 585 420 505 500"/>
|
|
32
|
+
</g>
|
|
33
|
+
</svg>
|
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]] = {
|
|
@@ -151,17 +156,38 @@ CONTEXT_CONFIG_MAP: Dict[str, Dict[str, str]] = {
|
|
|
151
156
|
# --- Helper Function ---
|
|
152
157
|
|
|
153
158
|
def _get_default_filename(command: str, output_key: str, basename: str, language: str, file_extension: str) -> str:
|
|
154
|
-
"""Generates the default filename based on the command and output key.
|
|
159
|
+
"""Generates the default filename based on the command and output key.
|
|
160
|
+
|
|
161
|
+
Supports subdirectory basenames like 'core/cloud'. When the basename contains
|
|
162
|
+
a forward slash, the directory structure is preserved in the output:
|
|
163
|
+
- Directory part (e.g., 'core/') is prepended to the final filename
|
|
164
|
+
- Pattern is applied only to the name part (e.g., 'cloud')
|
|
165
|
+
|
|
166
|
+
Example: basename='core/cloud', pattern='test_{basename}{ext}'
|
|
167
|
+
Result: 'core/test_cloud.py' (NOT 'test_core/cloud.py')
|
|
168
|
+
"""
|
|
155
169
|
try:
|
|
170
|
+
# Split basename into directory and name components for subdirectory support
|
|
171
|
+
if '/' in basename:
|
|
172
|
+
dir_part, name_part = basename.rsplit('/', 1)
|
|
173
|
+
dir_prefix = dir_part + '/'
|
|
174
|
+
else:
|
|
175
|
+
dir_prefix = ''
|
|
176
|
+
name_part = basename
|
|
177
|
+
|
|
156
178
|
pattern = DEFAULT_FILENAMES[command][output_key]
|
|
179
|
+
|
|
157
180
|
# Use specific extension if in pattern, otherwise use language extension
|
|
158
181
|
if '{ext}' in pattern:
|
|
159
|
-
|
|
182
|
+
# Ensure file_extension starts with '.' if not empty
|
|
160
183
|
effective_extension = file_extension if file_extension.startswith('.') or not file_extension else '.' + file_extension
|
|
161
|
-
|
|
184
|
+
filename = pattern.format(basename=name_part, language=language, ext=effective_extension)
|
|
162
185
|
else:
|
|
163
186
|
# Pattern already contains the full extension (e.g., .prompt, .log, .csv)
|
|
164
|
-
|
|
187
|
+
filename = pattern.format(basename=name_part, language=language)
|
|
188
|
+
|
|
189
|
+
# Prepend directory part to preserve subdirectory structure
|
|
190
|
+
return dir_prefix + filename
|
|
165
191
|
except KeyError:
|
|
166
192
|
logger.error(f"Default filename pattern not found for command '{command}', output key '{output_key}'.")
|
|
167
193
|
# Fallback or raise error - returning a basic fallback for now
|
|
@@ -178,14 +204,19 @@ def generate_output_paths(
|
|
|
178
204
|
basename: str,
|
|
179
205
|
language: str,
|
|
180
206
|
file_extension: str,
|
|
181
|
-
context_config: Optional[Dict[str, str]] = None
|
|
207
|
+
context_config: Optional[Dict[str, str]] = None,
|
|
208
|
+
input_file_dir: Optional[str] = None,
|
|
209
|
+
input_file_dirs: Optional[Dict[str, str]] = None,
|
|
210
|
+
config_base_dir: Optional[str] = None,
|
|
211
|
+
path_resolution_mode: PathResolutionMode = "config_base",
|
|
182
212
|
) -> Dict[str, str]:
|
|
183
213
|
"""
|
|
184
214
|
Generates the full, absolute output paths for a given PDD command.
|
|
185
215
|
|
|
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
|
|
216
|
+
It prioritizes user-specified paths (--output options), then context
|
|
217
|
+
configuration from .pddrc, then environment variables, and finally
|
|
218
|
+
falls back to default naming conventions in the input file's directory
|
|
219
|
+
(or current working directory if input_file_dir is not provided).
|
|
189
220
|
|
|
190
221
|
Args:
|
|
191
222
|
command: The PDD command being executed (e.g., 'generate', 'fix').
|
|
@@ -199,6 +230,23 @@ def generate_output_paths(
|
|
|
199
230
|
used when default patterns require it.
|
|
200
231
|
context_config: Optional dictionary with context-specific paths from .pddrc
|
|
201
232
|
configuration (e.g., {'generate_output_path': 'src/'}).
|
|
233
|
+
input_file_dir: Optional path to the input file's directory. When provided,
|
|
234
|
+
default output files will be placed in this directory instead
|
|
235
|
+
of the current working directory.
|
|
236
|
+
input_file_dirs: Optional dictionary mapping output keys to specific input
|
|
237
|
+
file directories. When provided, each output will use its
|
|
238
|
+
corresponding input file directory (e.g., {'output_code': 'src/main/java'}).
|
|
239
|
+
config_base_dir: Optional base directory to resolve relative `.pddrc` and
|
|
240
|
+
environment variable output paths. When set, relative
|
|
241
|
+
config paths resolve under this directory (typically the
|
|
242
|
+
directory containing `.pddrc`) instead of the input file
|
|
243
|
+
directory.
|
|
244
|
+
path_resolution_mode: Controls how relative paths from `.pddrc` and
|
|
245
|
+
environment variables are resolved. "config_base"
|
|
246
|
+
(default) resolves relative to config_base_dir,
|
|
247
|
+
"cwd" resolves relative to the current working
|
|
248
|
+
directory. Use "cwd" for sync command to ensure
|
|
249
|
+
output files are created where the user is.
|
|
202
250
|
|
|
203
251
|
Returns:
|
|
204
252
|
A dictionary where keys are the standardized output identifiers
|
|
@@ -209,9 +257,14 @@ def generate_output_paths(
|
|
|
209
257
|
logger.debug(f"Generating output paths for command: {command}")
|
|
210
258
|
logger.debug(f"User output locations: {output_locations}")
|
|
211
259
|
logger.debug(f"Context config: {context_config}")
|
|
260
|
+
logger.debug(f"Input file dirs: {input_file_dirs}")
|
|
261
|
+
logger.debug(f"Config base dir: {config_base_dir}")
|
|
262
|
+
logger.debug(f"Path resolution mode: {path_resolution_mode}")
|
|
212
263
|
logger.debug(f"Basename: {basename}, Language: {language}, Extension: {file_extension}")
|
|
213
264
|
|
|
214
265
|
context_config = context_config or {}
|
|
266
|
+
input_file_dirs = input_file_dirs or {}
|
|
267
|
+
config_base_dir_abs = os.path.abspath(config_base_dir) if config_base_dir else None
|
|
215
268
|
result_paths: Dict[str, str] = {}
|
|
216
269
|
|
|
217
270
|
if not basename:
|
|
@@ -278,6 +331,25 @@ def generate_output_paths(
|
|
|
278
331
|
# 2. Check Context Configuration Path (.pddrc)
|
|
279
332
|
elif context_path:
|
|
280
333
|
source = "context"
|
|
334
|
+
|
|
335
|
+
# Check if the ORIGINAL context path ends with / (explicit complete directory)
|
|
336
|
+
# When user configures "context/" or "backend/functions/utils/", they mean
|
|
337
|
+
# "put files directly here" - don't add dir_prefix from basename
|
|
338
|
+
original_context_path_ends_with_slash = context_path.endswith('/')
|
|
339
|
+
|
|
340
|
+
# Resolve relative `.pddrc` paths based on path_resolution_mode.
|
|
341
|
+
# "cwd" mode: resolve relative to current working directory (for sync)
|
|
342
|
+
# "config_base" mode: resolve relative to config_base_dir (for fix, etc.)
|
|
343
|
+
# Fall back to the input file directory for backwards compatibility.
|
|
344
|
+
if not os.path.isabs(context_path):
|
|
345
|
+
if path_resolution_mode == "cwd":
|
|
346
|
+
context_path = os.path.join(os.getcwd(), context_path)
|
|
347
|
+
elif config_base_dir_abs:
|
|
348
|
+
context_path = os.path.join(config_base_dir_abs, context_path)
|
|
349
|
+
elif input_file_dir:
|
|
350
|
+
context_path = os.path.join(input_file_dir, context_path)
|
|
351
|
+
logger.debug(f"Resolved relative context path to: {context_path}")
|
|
352
|
+
|
|
281
353
|
# Check if the context path is a directory
|
|
282
354
|
is_dir = context_path.endswith(os.path.sep) or context_path.endswith('/')
|
|
283
355
|
if not is_dir:
|
|
@@ -289,7 +361,22 @@ def generate_output_paths(
|
|
|
289
361
|
|
|
290
362
|
if is_dir:
|
|
291
363
|
logger.debug(f"Context path '{context_path}' identified as a directory.")
|
|
292
|
-
|
|
364
|
+
# When the config path explicitly ends with /, it's a complete directory
|
|
365
|
+
# Don't add dir_prefix - generate filename with just the name part
|
|
366
|
+
if original_context_path_ends_with_slash:
|
|
367
|
+
# Extract just the name part without dir_prefix
|
|
368
|
+
if '/' in basename:
|
|
369
|
+
_, name_part = basename.rsplit('/', 1)
|
|
370
|
+
else:
|
|
371
|
+
name_part = basename
|
|
372
|
+
# Generate filename without dir_prefix
|
|
373
|
+
filename_without_prefix = _get_default_filename(
|
|
374
|
+
command, output_key, name_part, language, file_extension
|
|
375
|
+
)
|
|
376
|
+
final_path = os.path.join(context_path, filename_without_prefix)
|
|
377
|
+
logger.debug(f"Using explicit directory without dir_prefix: {final_path}")
|
|
378
|
+
else:
|
|
379
|
+
final_path = os.path.join(context_path, default_filename)
|
|
293
380
|
else:
|
|
294
381
|
logger.debug(f"Context path '{context_path}' identified as a specific file path.")
|
|
295
382
|
final_path = context_path
|
|
@@ -297,6 +384,18 @@ def generate_output_paths(
|
|
|
297
384
|
# 3. Check Environment Variable Path
|
|
298
385
|
elif env_path:
|
|
299
386
|
source = "environment"
|
|
387
|
+
|
|
388
|
+
# Resolve relative env paths based on path_resolution_mode.
|
|
389
|
+
# Same logic as .pddrc paths for consistency.
|
|
390
|
+
if not os.path.isabs(env_path):
|
|
391
|
+
if path_resolution_mode == "cwd":
|
|
392
|
+
env_path = os.path.join(os.getcwd(), env_path)
|
|
393
|
+
elif config_base_dir_abs:
|
|
394
|
+
env_path = os.path.join(config_base_dir_abs, env_path)
|
|
395
|
+
elif input_file_dir:
|
|
396
|
+
env_path = os.path.join(input_file_dir, env_path)
|
|
397
|
+
logger.debug(f"Resolved relative env path to: {env_path}")
|
|
398
|
+
|
|
300
399
|
# Check if the environment variable points to a directory
|
|
301
400
|
is_dir = env_path.endswith(os.path.sep)
|
|
302
401
|
if not is_dir:
|
|
@@ -313,11 +412,33 @@ def generate_output_paths(
|
|
|
313
412
|
logger.debug(f"Env path '{env_path}' identified as a specific file path.")
|
|
314
413
|
final_path = env_path # Assume it's a full path or filename
|
|
315
414
|
|
|
316
|
-
# 4. Use Default Naming Convention
|
|
415
|
+
# 4. Use Default Naming Convention
|
|
317
416
|
else:
|
|
318
417
|
source = "default"
|
|
319
|
-
|
|
320
|
-
|
|
418
|
+
# For example command, default to examples/ directory if no .pddrc config
|
|
419
|
+
if command == "example":
|
|
420
|
+
examples_dir = EXAMPLES_DIR # Fallback constant
|
|
421
|
+
# Create examples directory if it doesn't exist
|
|
422
|
+
try:
|
|
423
|
+
os.makedirs(examples_dir, exist_ok=True)
|
|
424
|
+
logger.debug(f"Created examples directory: {examples_dir}")
|
|
425
|
+
except OSError as e:
|
|
426
|
+
logger.warning(f"Could not create examples directory: {e}")
|
|
427
|
+
final_path = os.path.join(examples_dir, default_filename)
|
|
428
|
+
logger.debug(f"Using default filename '{default_filename}' in examples directory.")
|
|
429
|
+
else:
|
|
430
|
+
# First check if there's a specific directory for this output key
|
|
431
|
+
specific_dir = input_file_dirs.get(output_key)
|
|
432
|
+
if specific_dir:
|
|
433
|
+
final_path = os.path.join(specific_dir, default_filename)
|
|
434
|
+
logger.debug(f"Using default filename '{default_filename}' in specific input file directory: {specific_dir}")
|
|
435
|
+
# Otherwise use the general input file directory if provided
|
|
436
|
+
elif input_file_dir:
|
|
437
|
+
final_path = os.path.join(input_file_dir, default_filename)
|
|
438
|
+
logger.debug(f"Using default filename '{default_filename}' in input file directory: {input_file_dir}")
|
|
439
|
+
else:
|
|
440
|
+
final_path = default_filename # Relative to CWD initially
|
|
441
|
+
logger.debug(f"Using default filename '{default_filename}' in current directory.")
|
|
321
442
|
|
|
322
443
|
# Resolve to absolute path
|
|
323
444
|
if final_path:
|
|
@@ -340,6 +461,9 @@ def generate_output_paths(
|
|
|
340
461
|
|
|
341
462
|
# --- Example Usage (for testing) ---
|
|
342
463
|
if __name__ == '__main__':
|
|
464
|
+
# Configure logging for standalone execution
|
|
465
|
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
466
|
+
|
|
343
467
|
# Mock inputs
|
|
344
468
|
mock_basename = "my_module"
|
|
345
469
|
mock_language = "python"
|
|
@@ -568,4 +692,4 @@ if __name__ == '__main__':
|
|
|
568
692
|
# 'output_code': '/path/to/cwd/another_module_verify_verified.py',
|
|
569
693
|
# 'output_program': f'/path/to/cwd/{env_verify_prog_path}'
|
|
570
694
|
# }
|
|
571
|
-
del os.environ['PDD_VERIFY_PROGRAM_OUTPUT_PATH'] # Clean up
|
|
695
|
+
del os.environ['PDD_VERIFY_PROGRAM_OUTPUT_PATH'] # Clean up
|