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.
Files changed (195) hide show
  1. pdd/__init__.py +40 -8
  2. pdd/agentic_bug.py +323 -0
  3. pdd/agentic_bug_orchestrator.py +497 -0
  4. pdd/agentic_change.py +231 -0
  5. pdd/agentic_change_orchestrator.py +526 -0
  6. pdd/agentic_common.py +598 -0
  7. pdd/agentic_crash.py +534 -0
  8. pdd/agentic_e2e_fix.py +319 -0
  9. pdd/agentic_e2e_fix_orchestrator.py +426 -0
  10. pdd/agentic_fix.py +1294 -0
  11. pdd/agentic_langtest.py +162 -0
  12. pdd/agentic_update.py +387 -0
  13. pdd/agentic_verify.py +183 -0
  14. pdd/architecture_sync.py +565 -0
  15. pdd/auth_service.py +210 -0
  16. pdd/auto_deps_main.py +71 -51
  17. pdd/auto_include.py +245 -5
  18. pdd/auto_update.py +125 -47
  19. pdd/bug_main.py +196 -23
  20. pdd/bug_to_unit_test.py +2 -0
  21. pdd/change_main.py +11 -4
  22. pdd/cli.py +22 -1181
  23. pdd/cmd_test_main.py +350 -150
  24. pdd/code_generator.py +60 -18
  25. pdd/code_generator_main.py +790 -57
  26. pdd/commands/__init__.py +48 -0
  27. pdd/commands/analysis.py +306 -0
  28. pdd/commands/auth.py +309 -0
  29. pdd/commands/connect.py +290 -0
  30. pdd/commands/fix.py +163 -0
  31. pdd/commands/generate.py +257 -0
  32. pdd/commands/maintenance.py +175 -0
  33. pdd/commands/misc.py +87 -0
  34. pdd/commands/modify.py +256 -0
  35. pdd/commands/report.py +144 -0
  36. pdd/commands/sessions.py +284 -0
  37. pdd/commands/templates.py +215 -0
  38. pdd/commands/utility.py +110 -0
  39. pdd/config_resolution.py +58 -0
  40. pdd/conflicts_main.py +8 -3
  41. pdd/construct_paths.py +589 -111
  42. pdd/context_generator.py +10 -2
  43. pdd/context_generator_main.py +175 -76
  44. pdd/continue_generation.py +53 -10
  45. pdd/core/__init__.py +33 -0
  46. pdd/core/cli.py +527 -0
  47. pdd/core/cloud.py +237 -0
  48. pdd/core/dump.py +554 -0
  49. pdd/core/errors.py +67 -0
  50. pdd/core/remote_session.py +61 -0
  51. pdd/core/utils.py +90 -0
  52. pdd/crash_main.py +262 -33
  53. pdd/data/language_format.csv +71 -63
  54. pdd/data/llm_model.csv +20 -18
  55. pdd/detect_change_main.py +5 -4
  56. pdd/docs/prompting_guide.md +864 -0
  57. pdd/docs/whitepaper_with_benchmarks/data_and_functions/benchmark_analysis.py +495 -0
  58. pdd/docs/whitepaper_with_benchmarks/data_and_functions/creation_compare.py +528 -0
  59. pdd/fix_code_loop.py +523 -95
  60. pdd/fix_code_module_errors.py +6 -2
  61. pdd/fix_error_loop.py +491 -92
  62. pdd/fix_errors_from_unit_tests.py +4 -3
  63. pdd/fix_main.py +278 -21
  64. pdd/fix_verification_errors.py +12 -100
  65. pdd/fix_verification_errors_loop.py +529 -286
  66. pdd/fix_verification_main.py +294 -89
  67. pdd/frontend/dist/assets/index-B5DZHykP.css +1 -0
  68. pdd/frontend/dist/assets/index-DQ3wkeQ2.js +449 -0
  69. pdd/frontend/dist/index.html +376 -0
  70. pdd/frontend/dist/logo.svg +33 -0
  71. pdd/generate_output_paths.py +139 -15
  72. pdd/generate_test.py +218 -146
  73. pdd/get_comment.py +19 -44
  74. pdd/get_extension.py +8 -9
  75. pdd/get_jwt_token.py +318 -22
  76. pdd/get_language.py +8 -7
  77. pdd/get_run_command.py +75 -0
  78. pdd/get_test_command.py +68 -0
  79. pdd/git_update.py +70 -19
  80. pdd/incremental_code_generator.py +2 -2
  81. pdd/insert_includes.py +13 -4
  82. pdd/llm_invoke.py +1711 -181
  83. pdd/load_prompt_template.py +19 -12
  84. pdd/path_resolution.py +140 -0
  85. pdd/pdd_completion.fish +25 -2
  86. pdd/pdd_completion.sh +30 -4
  87. pdd/pdd_completion.zsh +79 -4
  88. pdd/postprocess.py +14 -4
  89. pdd/preprocess.py +293 -24
  90. pdd/preprocess_main.py +41 -6
  91. pdd/prompts/agentic_bug_step10_pr_LLM.prompt +182 -0
  92. pdd/prompts/agentic_bug_step1_duplicate_LLM.prompt +73 -0
  93. pdd/prompts/agentic_bug_step2_docs_LLM.prompt +129 -0
  94. pdd/prompts/agentic_bug_step3_triage_LLM.prompt +95 -0
  95. pdd/prompts/agentic_bug_step4_reproduce_LLM.prompt +97 -0
  96. pdd/prompts/agentic_bug_step5_root_cause_LLM.prompt +123 -0
  97. pdd/prompts/agentic_bug_step6_test_plan_LLM.prompt +107 -0
  98. pdd/prompts/agentic_bug_step7_generate_LLM.prompt +172 -0
  99. pdd/prompts/agentic_bug_step8_verify_LLM.prompt +119 -0
  100. pdd/prompts/agentic_bug_step9_e2e_test_LLM.prompt +289 -0
  101. pdd/prompts/agentic_change_step10_identify_issues_LLM.prompt +1006 -0
  102. pdd/prompts/agentic_change_step11_fix_issues_LLM.prompt +984 -0
  103. pdd/prompts/agentic_change_step12_create_pr_LLM.prompt +131 -0
  104. pdd/prompts/agentic_change_step1_duplicate_LLM.prompt +73 -0
  105. pdd/prompts/agentic_change_step2_docs_LLM.prompt +101 -0
  106. pdd/prompts/agentic_change_step3_research_LLM.prompt +126 -0
  107. pdd/prompts/agentic_change_step4_clarify_LLM.prompt +164 -0
  108. pdd/prompts/agentic_change_step5_docs_change_LLM.prompt +981 -0
  109. pdd/prompts/agentic_change_step6_devunits_LLM.prompt +1005 -0
  110. pdd/prompts/agentic_change_step7_architecture_LLM.prompt +1044 -0
  111. pdd/prompts/agentic_change_step8_analyze_LLM.prompt +1027 -0
  112. pdd/prompts/agentic_change_step9_implement_LLM.prompt +1077 -0
  113. pdd/prompts/agentic_crash_explore_LLM.prompt +49 -0
  114. pdd/prompts/agentic_e2e_fix_step1_unit_tests_LLM.prompt +90 -0
  115. pdd/prompts/agentic_e2e_fix_step2_e2e_tests_LLM.prompt +91 -0
  116. pdd/prompts/agentic_e2e_fix_step3_root_cause_LLM.prompt +89 -0
  117. pdd/prompts/agentic_e2e_fix_step4_fix_e2e_tests_LLM.prompt +96 -0
  118. pdd/prompts/agentic_e2e_fix_step5_identify_devunits_LLM.prompt +91 -0
  119. pdd/prompts/agentic_e2e_fix_step6_create_unit_tests_LLM.prompt +106 -0
  120. pdd/prompts/agentic_e2e_fix_step7_verify_tests_LLM.prompt +116 -0
  121. pdd/prompts/agentic_e2e_fix_step8_run_pdd_fix_LLM.prompt +120 -0
  122. pdd/prompts/agentic_e2e_fix_step9_verify_all_LLM.prompt +146 -0
  123. pdd/prompts/agentic_fix_explore_LLM.prompt +45 -0
  124. pdd/prompts/agentic_fix_harvest_only_LLM.prompt +48 -0
  125. pdd/prompts/agentic_fix_primary_LLM.prompt +85 -0
  126. pdd/prompts/agentic_update_LLM.prompt +925 -0
  127. pdd/prompts/agentic_verify_explore_LLM.prompt +45 -0
  128. pdd/prompts/auto_include_LLM.prompt +122 -905
  129. pdd/prompts/change_LLM.prompt +3093 -1
  130. pdd/prompts/detect_change_LLM.prompt +686 -27
  131. pdd/prompts/example_generator_LLM.prompt +22 -1
  132. pdd/prompts/extract_code_LLM.prompt +5 -1
  133. pdd/prompts/extract_program_code_fix_LLM.prompt +7 -1
  134. pdd/prompts/extract_prompt_update_LLM.prompt +7 -8
  135. pdd/prompts/extract_promptline_LLM.prompt +17 -11
  136. pdd/prompts/find_verification_errors_LLM.prompt +6 -0
  137. pdd/prompts/fix_code_module_errors_LLM.prompt +12 -2
  138. pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +9 -0
  139. pdd/prompts/fix_verification_errors_LLM.prompt +22 -0
  140. pdd/prompts/generate_test_LLM.prompt +41 -7
  141. pdd/prompts/generate_test_from_example_LLM.prompt +115 -0
  142. pdd/prompts/increase_tests_LLM.prompt +1 -5
  143. pdd/prompts/insert_includes_LLM.prompt +316 -186
  144. pdd/prompts/prompt_code_diff_LLM.prompt +119 -0
  145. pdd/prompts/prompt_diff_LLM.prompt +82 -0
  146. pdd/prompts/trace_LLM.prompt +25 -22
  147. pdd/prompts/unfinished_prompt_LLM.prompt +85 -1
  148. pdd/prompts/update_prompt_LLM.prompt +22 -1
  149. pdd/pytest_output.py +127 -12
  150. pdd/remote_session.py +876 -0
  151. pdd/render_mermaid.py +236 -0
  152. pdd/server/__init__.py +52 -0
  153. pdd/server/app.py +335 -0
  154. pdd/server/click_executor.py +587 -0
  155. pdd/server/executor.py +338 -0
  156. pdd/server/jobs.py +661 -0
  157. pdd/server/models.py +241 -0
  158. pdd/server/routes/__init__.py +31 -0
  159. pdd/server/routes/architecture.py +451 -0
  160. pdd/server/routes/auth.py +364 -0
  161. pdd/server/routes/commands.py +929 -0
  162. pdd/server/routes/config.py +42 -0
  163. pdd/server/routes/files.py +603 -0
  164. pdd/server/routes/prompts.py +1322 -0
  165. pdd/server/routes/websocket.py +473 -0
  166. pdd/server/security.py +243 -0
  167. pdd/server/terminal_spawner.py +209 -0
  168. pdd/server/token_counter.py +222 -0
  169. pdd/setup_tool.py +648 -0
  170. pdd/simple_math.py +2 -0
  171. pdd/split_main.py +3 -2
  172. pdd/summarize_directory.py +237 -195
  173. pdd/sync_animation.py +8 -4
  174. pdd/sync_determine_operation.py +839 -112
  175. pdd/sync_main.py +351 -57
  176. pdd/sync_orchestration.py +1400 -756
  177. pdd/sync_tui.py +848 -0
  178. pdd/template_expander.py +161 -0
  179. pdd/template_registry.py +264 -0
  180. pdd/templates/architecture/architecture_json.prompt +237 -0
  181. pdd/templates/generic/generate_prompt.prompt +174 -0
  182. pdd/trace.py +168 -12
  183. pdd/trace_main.py +4 -3
  184. pdd/track_cost.py +140 -63
  185. pdd/unfinished_prompt.py +51 -4
  186. pdd/update_main.py +567 -67
  187. pdd/update_model_costs.py +2 -2
  188. pdd/update_prompt.py +19 -4
  189. {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/METADATA +29 -11
  190. pdd_cli-0.0.118.dist-info/RECORD +227 -0
  191. {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/licenses/LICENSE +1 -1
  192. pdd_cli-0.0.45.dist-info/RECORD +0 -116
  193. {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/WHEEL +0 -0
  194. {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/entry_points.txt +0 -0
  195. {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>
@@ -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
- # Ensure file_extension starts with '.' if not empty
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
- return pattern.format(basename=basename, language=language, ext=effective_extension)
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
- return pattern.format(basename=basename, language=language) # ext might not be needed
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 current working directory.
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
- final_path = os.path.join(context_path, default_filename)
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 in CWD
415
+ # 4. Use Default Naming Convention
317
416
  else:
318
417
  source = "default"
319
- logger.debug(f"Using default filename '{default_filename}' in current directory.")
320
- final_path = default_filename # Relative to CWD initially
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