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.
Files changed (151) hide show
  1. pdd/__init__.py +38 -6
  2. pdd/agentic_bug.py +323 -0
  3. pdd/agentic_bug_orchestrator.py +506 -0
  4. pdd/agentic_change.py +231 -0
  5. pdd/agentic_change_orchestrator.py +537 -0
  6. pdd/agentic_common.py +533 -770
  7. pdd/agentic_crash.py +2 -1
  8. pdd/agentic_e2e_fix.py +319 -0
  9. pdd/agentic_e2e_fix_orchestrator.py +582 -0
  10. pdd/agentic_fix.py +118 -3
  11. pdd/agentic_update.py +27 -9
  12. pdd/agentic_verify.py +3 -2
  13. pdd/architecture_sync.py +565 -0
  14. pdd/auth_service.py +210 -0
  15. pdd/auto_deps_main.py +63 -53
  16. pdd/auto_include.py +236 -3
  17. pdd/auto_update.py +125 -47
  18. pdd/bug_main.py +195 -23
  19. pdd/cmd_test_main.py +345 -197
  20. pdd/code_generator.py +4 -2
  21. pdd/code_generator_main.py +118 -32
  22. pdd/commands/__init__.py +6 -0
  23. pdd/commands/analysis.py +113 -48
  24. pdd/commands/auth.py +309 -0
  25. pdd/commands/connect.py +358 -0
  26. pdd/commands/fix.py +155 -114
  27. pdd/commands/generate.py +5 -0
  28. pdd/commands/maintenance.py +3 -2
  29. pdd/commands/misc.py +8 -0
  30. pdd/commands/modify.py +225 -163
  31. pdd/commands/sessions.py +284 -0
  32. pdd/commands/utility.py +12 -7
  33. pdd/construct_paths.py +334 -32
  34. pdd/context_generator_main.py +167 -170
  35. pdd/continue_generation.py +6 -3
  36. pdd/core/__init__.py +33 -0
  37. pdd/core/cli.py +44 -7
  38. pdd/core/cloud.py +237 -0
  39. pdd/core/dump.py +68 -20
  40. pdd/core/errors.py +4 -0
  41. pdd/core/remote_session.py +61 -0
  42. pdd/crash_main.py +219 -23
  43. pdd/data/llm_model.csv +4 -4
  44. pdd/docs/prompting_guide.md +864 -0
  45. pdd/docs/whitepaper_with_benchmarks/data_and_functions/benchmark_analysis.py +495 -0
  46. pdd/docs/whitepaper_with_benchmarks/data_and_functions/creation_compare.py +528 -0
  47. pdd/fix_code_loop.py +208 -34
  48. pdd/fix_code_module_errors.py +6 -2
  49. pdd/fix_error_loop.py +291 -38
  50. pdd/fix_main.py +208 -6
  51. pdd/fix_verification_errors_loop.py +235 -26
  52. pdd/fix_verification_main.py +269 -83
  53. pdd/frontend/dist/assets/index-B5DZHykP.css +1 -0
  54. pdd/frontend/dist/assets/index-CUWd8al1.js +450 -0
  55. pdd/frontend/dist/index.html +376 -0
  56. pdd/frontend/dist/logo.svg +33 -0
  57. pdd/generate_output_paths.py +46 -5
  58. pdd/generate_test.py +212 -151
  59. pdd/get_comment.py +19 -44
  60. pdd/get_extension.py +8 -9
  61. pdd/get_jwt_token.py +309 -20
  62. pdd/get_language.py +8 -7
  63. pdd/get_run_command.py +7 -5
  64. pdd/insert_includes.py +2 -1
  65. pdd/llm_invoke.py +531 -97
  66. pdd/load_prompt_template.py +15 -34
  67. pdd/operation_log.py +342 -0
  68. pdd/path_resolution.py +140 -0
  69. pdd/postprocess.py +122 -97
  70. pdd/preprocess.py +68 -12
  71. pdd/preprocess_main.py +33 -1
  72. pdd/prompts/agentic_bug_step10_pr_LLM.prompt +182 -0
  73. pdd/prompts/agentic_bug_step1_duplicate_LLM.prompt +73 -0
  74. pdd/prompts/agentic_bug_step2_docs_LLM.prompt +129 -0
  75. pdd/prompts/agentic_bug_step3_triage_LLM.prompt +95 -0
  76. pdd/prompts/agentic_bug_step4_reproduce_LLM.prompt +97 -0
  77. pdd/prompts/agentic_bug_step5_root_cause_LLM.prompt +123 -0
  78. pdd/prompts/agentic_bug_step6_test_plan_LLM.prompt +107 -0
  79. pdd/prompts/agentic_bug_step7_generate_LLM.prompt +172 -0
  80. pdd/prompts/agentic_bug_step8_verify_LLM.prompt +119 -0
  81. pdd/prompts/agentic_bug_step9_e2e_test_LLM.prompt +289 -0
  82. pdd/prompts/agentic_change_step10_identify_issues_LLM.prompt +1006 -0
  83. pdd/prompts/agentic_change_step11_fix_issues_LLM.prompt +984 -0
  84. pdd/prompts/agentic_change_step12_create_pr_LLM.prompt +140 -0
  85. pdd/prompts/agentic_change_step1_duplicate_LLM.prompt +73 -0
  86. pdd/prompts/agentic_change_step2_docs_LLM.prompt +101 -0
  87. pdd/prompts/agentic_change_step3_research_LLM.prompt +126 -0
  88. pdd/prompts/agentic_change_step4_clarify_LLM.prompt +164 -0
  89. pdd/prompts/agentic_change_step5_docs_change_LLM.prompt +981 -0
  90. pdd/prompts/agentic_change_step6_devunits_LLM.prompt +1005 -0
  91. pdd/prompts/agentic_change_step7_architecture_LLM.prompt +1044 -0
  92. pdd/prompts/agentic_change_step8_analyze_LLM.prompt +1027 -0
  93. pdd/prompts/agentic_change_step9_implement_LLM.prompt +1077 -0
  94. pdd/prompts/agentic_e2e_fix_step1_unit_tests_LLM.prompt +90 -0
  95. pdd/prompts/agentic_e2e_fix_step2_e2e_tests_LLM.prompt +91 -0
  96. pdd/prompts/agentic_e2e_fix_step3_root_cause_LLM.prompt +89 -0
  97. pdd/prompts/agentic_e2e_fix_step4_fix_e2e_tests_LLM.prompt +96 -0
  98. pdd/prompts/agentic_e2e_fix_step5_identify_devunits_LLM.prompt +91 -0
  99. pdd/prompts/agentic_e2e_fix_step6_create_unit_tests_LLM.prompt +106 -0
  100. pdd/prompts/agentic_e2e_fix_step7_verify_tests_LLM.prompt +116 -0
  101. pdd/prompts/agentic_e2e_fix_step8_run_pdd_fix_LLM.prompt +120 -0
  102. pdd/prompts/agentic_e2e_fix_step9_verify_all_LLM.prompt +146 -0
  103. pdd/prompts/agentic_fix_primary_LLM.prompt +2 -2
  104. pdd/prompts/agentic_update_LLM.prompt +192 -338
  105. pdd/prompts/auto_include_LLM.prompt +22 -0
  106. pdd/prompts/change_LLM.prompt +3093 -1
  107. pdd/prompts/detect_change_LLM.prompt +571 -14
  108. pdd/prompts/fix_code_module_errors_LLM.prompt +8 -0
  109. pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +1 -0
  110. pdd/prompts/generate_test_LLM.prompt +19 -1
  111. pdd/prompts/generate_test_from_example_LLM.prompt +366 -0
  112. pdd/prompts/insert_includes_LLM.prompt +262 -252
  113. pdd/prompts/prompt_code_diff_LLM.prompt +123 -0
  114. pdd/prompts/prompt_diff_LLM.prompt +82 -0
  115. pdd/remote_session.py +876 -0
  116. pdd/server/__init__.py +52 -0
  117. pdd/server/app.py +335 -0
  118. pdd/server/click_executor.py +587 -0
  119. pdd/server/executor.py +338 -0
  120. pdd/server/jobs.py +661 -0
  121. pdd/server/models.py +241 -0
  122. pdd/server/routes/__init__.py +31 -0
  123. pdd/server/routes/architecture.py +451 -0
  124. pdd/server/routes/auth.py +364 -0
  125. pdd/server/routes/commands.py +929 -0
  126. pdd/server/routes/config.py +42 -0
  127. pdd/server/routes/files.py +603 -0
  128. pdd/server/routes/prompts.py +1347 -0
  129. pdd/server/routes/websocket.py +473 -0
  130. pdd/server/security.py +243 -0
  131. pdd/server/terminal_spawner.py +217 -0
  132. pdd/server/token_counter.py +222 -0
  133. pdd/summarize_directory.py +236 -237
  134. pdd/sync_animation.py +8 -4
  135. pdd/sync_determine_operation.py +329 -47
  136. pdd/sync_main.py +272 -28
  137. pdd/sync_orchestration.py +289 -211
  138. pdd/sync_order.py +304 -0
  139. pdd/template_expander.py +161 -0
  140. pdd/templates/architecture/architecture_json.prompt +41 -46
  141. pdd/trace.py +1 -1
  142. pdd/track_cost.py +0 -13
  143. pdd/unfinished_prompt.py +2 -1
  144. pdd/update_main.py +68 -26
  145. {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/METADATA +15 -10
  146. pdd_cli-0.0.121.dist-info/RECORD +229 -0
  147. pdd_cli-0.0.90.dist-info/RECORD +0 -153
  148. {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/WHEEL +0 -0
  149. {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/entry_points.txt +0 -0
  150. {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/licenses/LICENSE +0 -0
  151. {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,284 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import json
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ import click
8
+ from rich.console import Console
9
+ from rich.table import Table
10
+
11
+ from ..core.cloud import CloudConfig
12
+ from ..remote_session import RemoteSessionManager, RemoteSessionError
13
+
14
+ console = Console()
15
+
16
+
17
+ @click.group(name="sessions")
18
+ def sessions() -> None:
19
+ """Manage remote PDD sessions."""
20
+ pass
21
+
22
+
23
+ @sessions.command(name="list")
24
+ @click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
25
+ def list_sessions(json_output: bool) -> None:
26
+ """List active remote sessions.
27
+
28
+ Retrieves a list of active remote sessions associated with the current
29
+ authenticated user and displays them in a table or as JSON.
30
+ """
31
+ jwt_token = CloudConfig.get_jwt_token()
32
+ if not jwt_token:
33
+ console.print("[red]Error: Not authenticated. Please run 'pdd auth login'.[/red]")
34
+ return
35
+
36
+ try:
37
+ sessions_list = asyncio.run(RemoteSessionManager.list_sessions(jwt_token))
38
+ except Exception as e:
39
+ console.print(f"[red]Error listing sessions: {e}[/red]")
40
+ return
41
+
42
+ if json_output:
43
+ output_data = []
44
+ for s in sessions_list:
45
+ # Handle Pydantic v1/v2 or dataclasses
46
+ if hasattr(s, "model_dump"):
47
+ output_data.append(s.model_dump())
48
+ elif hasattr(s, "dict"):
49
+ output_data.append(s.dict())
50
+ else:
51
+ output_data.append(s.__dict__)
52
+ console.print_json(data=output_data)
53
+ return
54
+
55
+ if not sessions_list:
56
+ console.print("[yellow]No active remote sessions found.[/yellow]")
57
+ return
58
+
59
+ table = Table(show_header=True, header_style="bold magenta")
60
+ table.add_column("SESSION ID", style="dim", width=12)
61
+ table.add_column("PROJECT")
62
+ table.add_column("CLOUD URL", style="blue")
63
+ table.add_column("STATUS")
64
+ table.add_column("LAST SEEN")
65
+
66
+ for session in sessions_list:
67
+ # Safely access attributes with defaults
68
+ s_id = getattr(session, "session_id", "unknown")
69
+ project = getattr(session, "project_name", "default")
70
+ url = getattr(session, "cloud_url", "")
71
+ status = getattr(session, "status", "unknown")
72
+ last_seen = getattr(session, "last_heartbeat", "never")
73
+
74
+ # Truncate ID for display
75
+ display_id = s_id[:8] if len(s_id) > 8 else s_id
76
+
77
+ # Colorize status
78
+ status_str = str(status)
79
+ if status_str.lower() == "active":
80
+ status_render = f"[green]{status_str}[/green]"
81
+ elif status_str.lower() == "stale":
82
+ status_render = f"[yellow]{status_str}[/yellow]"
83
+ else:
84
+ status_render = status_str
85
+
86
+ table.add_row(
87
+ display_id,
88
+ str(project),
89
+ str(url),
90
+ status_render,
91
+ str(last_seen)
92
+ )
93
+
94
+ console.print(table)
95
+
96
+
97
+ @sessions.command(name="info")
98
+ @click.argument("session_id")
99
+ def session_info(session_id: str) -> None:
100
+ """Display detailed info about a specific session.
101
+
102
+ Args:
103
+ session_id: The unique identifier of the session to inspect.
104
+ """
105
+ jwt_token = CloudConfig.get_jwt_token()
106
+ if not jwt_token:
107
+ console.print("[red]Error: Not authenticated. Please run 'pdd auth login'.[/red]")
108
+ return
109
+
110
+ try:
111
+ # Attempt to fetch specific session details
112
+ # Note: Assuming get_session exists on RemoteSessionManager
113
+ session = asyncio.run(RemoteSessionManager.get_session(jwt_token, session_id))
114
+ except Exception as e:
115
+ console.print(f"[red]Error fetching session: {e}[/red]")
116
+ return
117
+
118
+ if not session:
119
+ console.print(f"[red]Session '{session_id}' not found.[/red]")
120
+ return
121
+
122
+ console.print(f"[bold blue]Session Information: {session_id}[/bold blue]")
123
+
124
+ # Convert session object to dictionary for iteration
125
+ if hasattr(session, "model_dump"):
126
+ data = session.model_dump()
127
+ elif hasattr(session, "dict"):
128
+ data = session.dict()
129
+ else:
130
+ data = session.__dict__
131
+
132
+ # Display metadata in a clean table
133
+ table = Table(show_header=False, box=None, padding=(0, 2))
134
+ table.add_column("Field", style="bold cyan", justify="right")
135
+ table.add_column("Value", style="white")
136
+
137
+ # Sort keys for consistent display
138
+ for key in sorted(data.keys()):
139
+ value = data[key]
140
+ # Format key for display (snake_case to Title Case)
141
+ display_key = key.replace("_", " ").title()
142
+ table.add_row(display_key, str(value))
143
+
144
+ console.print(table)
145
+
146
+
147
+ @sessions.command(name="cleanup")
148
+ @click.option("--all", "cleanup_all", is_flag=True, help="Cleanup all sessions (including active).")
149
+ @click.option("--stale", "cleanup_stale", is_flag=True, help="Cleanup only stale sessions.")
150
+ @click.option("--force", is_flag=True, help="Skip confirmation prompt.")
151
+ def cleanup_sessions(cleanup_all: bool, cleanup_stale: bool, force: bool) -> None:
152
+ """Cleanup (deregister) remote sessions.
153
+
154
+ By default, lists sessions and prompts for cleanup.
155
+ Use --all to cleanup all sessions, or --stale to cleanup only stale sessions.
156
+ """
157
+ jwt_token = CloudConfig.get_jwt_token()
158
+ if not jwt_token:
159
+ console.print("[red]Error: Not authenticated. Please run 'pdd login'.[/red]")
160
+ return
161
+
162
+ try:
163
+ sessions_list = asyncio.run(RemoteSessionManager.list_sessions(jwt_token))
164
+ except Exception as e:
165
+ console.print(f"[red]Error listing sessions: {e}[/red]")
166
+ return
167
+
168
+ if not sessions_list:
169
+ console.print("[yellow]No active remote sessions found.[/yellow]")
170
+ return
171
+
172
+ # Filter sessions based on flags
173
+ if cleanup_stale:
174
+ sessions_to_cleanup = [s for s in sessions_list if getattr(s, "status", "").lower() == "stale"]
175
+ if not sessions_to_cleanup:
176
+ console.print("[yellow]No stale sessions found.[/yellow]")
177
+ return
178
+ elif cleanup_all:
179
+ sessions_to_cleanup = sessions_list
180
+ else:
181
+ # Interactive mode - show sessions and ask which to cleanup
182
+ console.print("[bold]Current remote sessions:[/bold]")
183
+ table = Table(show_header=True, header_style="bold magenta")
184
+ table.add_column("#", style="dim", width=3)
185
+ table.add_column("SESSION ID", style="dim", width=12)
186
+ table.add_column("PROJECT")
187
+ table.add_column("STATUS")
188
+ table.add_column("LAST SEEN")
189
+
190
+ for idx, session in enumerate(sessions_list, 1):
191
+ s_id = getattr(session, "session_id", "unknown")
192
+ project = getattr(session, "project_name", "default")
193
+ status = getattr(session, "status", "unknown")
194
+ last_seen = getattr(session, "last_heartbeat", "never")
195
+
196
+ display_id = s_id[:8] if len(s_id) > 8 else s_id
197
+
198
+ status_str = str(status)
199
+ if status_str.lower() == "active":
200
+ status_render = f"[green]{status_str}[/green]"
201
+ elif status_str.lower() == "stale":
202
+ status_render = f"[yellow]{status_str}[/yellow]"
203
+ else:
204
+ status_render = status_str
205
+
206
+ table.add_row(
207
+ str(idx),
208
+ display_id,
209
+ str(project),
210
+ status_render,
211
+ str(last_seen)
212
+ )
213
+
214
+ console.print(table)
215
+ console.print("\n[bold]Options:[/bold]")
216
+ console.print(" - Enter session numbers (comma-separated) to cleanup specific sessions")
217
+ console.print(" - Enter 'stale' to cleanup all stale sessions")
218
+ console.print(" - Enter 'all' to cleanup all sessions")
219
+ console.print(" - Press Enter to cancel")
220
+
221
+ choice = click.prompt("\nYour choice", default="", show_default=False)
222
+
223
+ if not choice:
224
+ console.print("[yellow]Cancelled.[/yellow]")
225
+ return
226
+
227
+ if choice.lower() == "all":
228
+ sessions_to_cleanup = sessions_list
229
+ elif choice.lower() == "stale":
230
+ sessions_to_cleanup = [s for s in sessions_list if getattr(s, "status", "").lower() == "stale"]
231
+ if not sessions_to_cleanup:
232
+ console.print("[yellow]No stale sessions found.[/yellow]")
233
+ return
234
+ else:
235
+ # Parse comma-separated numbers
236
+ try:
237
+ indices = [int(x.strip()) - 1 for x in choice.split(",")]
238
+ sessions_to_cleanup = [sessions_list[i] for i in indices if 0 <= i < len(sessions_list)]
239
+ if not sessions_to_cleanup:
240
+ console.print("[red]Invalid selection.[/red]")
241
+ return
242
+ except (ValueError, IndexError):
243
+ console.print("[red]Invalid input. Please enter numbers separated by commas.[/red]")
244
+ return
245
+
246
+ # Confirm cleanup
247
+ if not force:
248
+ console.print(f"\n[bold yellow]About to cleanup {len(sessions_to_cleanup)} session(s):[/bold yellow]")
249
+ for session in sessions_to_cleanup:
250
+ s_id = getattr(session, "session_id", "unknown")
251
+ project = getattr(session, "project_name", "default")
252
+ console.print(f" - {s_id[:8]} ({project})")
253
+
254
+ if not click.confirm("\nProceed with cleanup?", default=False):
255
+ console.print("[yellow]Cancelled.[/yellow]")
256
+ return
257
+
258
+ # Perform cleanup
259
+ success_count = 0
260
+ fail_count = 0
261
+
262
+ async def cleanup_session(session_id: str) -> bool:
263
+ """Helper to deregister a single session."""
264
+ from pathlib import Path
265
+ manager = RemoteSessionManager(jwt_token, project_path=Path.cwd())
266
+ manager.session_id = session_id
267
+ try:
268
+ await manager.deregister()
269
+ return True
270
+ except Exception as e:
271
+ console.print(f"[red]Failed to cleanup {session_id[:8]}: {e}[/red]")
272
+ return False
273
+
274
+ with console.status("[bold green]Cleaning up sessions..."):
275
+ for session in sessions_to_cleanup:
276
+ s_id = getattr(session, "session_id", "unknown")
277
+ if asyncio.run(cleanup_session(s_id)):
278
+ success_count += 1
279
+ else:
280
+ fail_count += 1
281
+
282
+ console.print(f"\n[bold green]✓[/bold green] Successfully cleaned up {success_count} session(s)")
283
+ if fail_count > 0:
284
+ console.print(f"[bold red]✗[/bold red] Failed to cleanup {fail_count} session(s)")
pdd/commands/utility.py CHANGED
@@ -1,24 +1,27 @@
1
1
  """
2
2
  Utility commands (install_completion, verify/fix-verification).
3
3
  """
4
+ from __future__ import annotations
4
5
  import click
5
- from typing import Optional, Tuple
6
+ from typing import Optional, Tuple, Dict, Any
6
7
 
7
8
  from ..fix_verification_main import fix_verification_main
8
9
  from ..track_cost import track_cost
9
10
  from ..core.errors import handle_error
11
+ from ..operation_log import log_operation
10
12
 
11
13
  @click.command("install_completion")
12
14
  @click.pass_context
13
- def install_completion_cmd(ctx: click.Context):
15
+ def install_completion_cmd(ctx: click.Context) -> None:
14
16
  """Install shell completion for the PDD CLI."""
17
+ # Safely retrieve quiet flag, defaulting to False if ctx.obj is None
18
+ quiet = (ctx.obj or {}).get("quiet", False)
15
19
  try:
16
20
  from .. import cli as cli_module # Import parent module for proper patching
17
- quiet = ctx.obj.get("quiet", False)
18
21
  # Call through cli_module so patches to pdd.cli.install_completion work
19
22
  cli_module.install_completion(quiet=quiet)
20
23
  except Exception as e:
21
- handle_error(e, "install_completion", ctx.obj.get("quiet", False))
24
+ handle_error(e, "install_completion", quiet)
22
25
 
23
26
 
24
27
  @click.command("verify")
@@ -64,6 +67,7 @@ def install_completion_cmd(ctx: click.Context):
64
67
  help="Enable agentic fallback if the primary fix mechanism fails.",
65
68
  )
66
69
  @click.pass_context
70
+ @log_operation(operation="verify", clears_run_report=True, updates_run_report=True)
67
71
  @track_cost
68
72
  def verify(
69
73
  ctx: click.Context,
@@ -76,7 +80,7 @@ def verify(
76
80
  max_attempts: int,
77
81
  budget: float,
78
82
  agentic_fallback: bool,
79
- ) -> Optional[Tuple]:
83
+ ) -> Optional[Tuple[Dict[str, Any], float, str]]:
80
84
  """Verify code using a verification program."""
81
85
  try:
82
86
  # verify command implies a loop if max_attempts > 1, but let's enable loop by default
@@ -106,5 +110,6 @@ def verify(
106
110
  except click.Abort:
107
111
  raise
108
112
  except Exception as exception:
109
- handle_error(exception, "verify", ctx.obj.get("quiet", False))
110
- return None
113
+ quiet = (ctx.obj or {}).get("quiet", False)
114
+ handle_error(exception, "verify", quiet)
115
+ return None