pdd-cli 0.0.90__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 (144) hide show
  1. pdd/__init__.py +38 -6
  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 +521 -786
  7. pdd/agentic_e2e_fix.py +319 -0
  8. pdd/agentic_e2e_fix_orchestrator.py +426 -0
  9. pdd/agentic_fix.py +118 -3
  10. pdd/agentic_update.py +25 -8
  11. pdd/architecture_sync.py +565 -0
  12. pdd/auth_service.py +210 -0
  13. pdd/auto_deps_main.py +63 -53
  14. pdd/auto_include.py +185 -3
  15. pdd/auto_update.py +125 -47
  16. pdd/bug_main.py +195 -23
  17. pdd/cmd_test_main.py +345 -197
  18. pdd/code_generator.py +4 -2
  19. pdd/code_generator_main.py +118 -32
  20. pdd/commands/__init__.py +6 -0
  21. pdd/commands/analysis.py +87 -29
  22. pdd/commands/auth.py +309 -0
  23. pdd/commands/connect.py +290 -0
  24. pdd/commands/fix.py +136 -113
  25. pdd/commands/maintenance.py +3 -2
  26. pdd/commands/misc.py +8 -0
  27. pdd/commands/modify.py +190 -164
  28. pdd/commands/sessions.py +284 -0
  29. pdd/construct_paths.py +334 -32
  30. pdd/context_generator_main.py +167 -170
  31. pdd/continue_generation.py +6 -3
  32. pdd/core/__init__.py +33 -0
  33. pdd/core/cli.py +27 -3
  34. pdd/core/cloud.py +237 -0
  35. pdd/core/errors.py +4 -0
  36. pdd/core/remote_session.py +61 -0
  37. pdd/crash_main.py +219 -23
  38. pdd/data/llm_model.csv +4 -4
  39. pdd/docs/prompting_guide.md +864 -0
  40. pdd/docs/whitepaper_with_benchmarks/data_and_functions/benchmark_analysis.py +495 -0
  41. pdd/docs/whitepaper_with_benchmarks/data_and_functions/creation_compare.py +528 -0
  42. pdd/fix_code_loop.py +208 -34
  43. pdd/fix_code_module_errors.py +6 -2
  44. pdd/fix_error_loop.py +291 -38
  45. pdd/fix_main.py +204 -4
  46. pdd/fix_verification_errors_loop.py +235 -26
  47. pdd/fix_verification_main.py +269 -83
  48. pdd/frontend/dist/assets/index-B5DZHykP.css +1 -0
  49. pdd/frontend/dist/assets/index-DQ3wkeQ2.js +449 -0
  50. pdd/frontend/dist/index.html +376 -0
  51. pdd/frontend/dist/logo.svg +33 -0
  52. pdd/generate_output_paths.py +46 -5
  53. pdd/generate_test.py +212 -151
  54. pdd/get_comment.py +19 -44
  55. pdd/get_extension.py +8 -9
  56. pdd/get_jwt_token.py +309 -20
  57. pdd/get_language.py +8 -7
  58. pdd/get_run_command.py +7 -5
  59. pdd/insert_includes.py +2 -1
  60. pdd/llm_invoke.py +459 -95
  61. pdd/load_prompt_template.py +15 -34
  62. pdd/path_resolution.py +140 -0
  63. pdd/postprocess.py +4 -1
  64. pdd/preprocess.py +68 -12
  65. pdd/preprocess_main.py +33 -1
  66. pdd/prompts/agentic_bug_step10_pr_LLM.prompt +182 -0
  67. pdd/prompts/agentic_bug_step1_duplicate_LLM.prompt +73 -0
  68. pdd/prompts/agentic_bug_step2_docs_LLM.prompt +129 -0
  69. pdd/prompts/agentic_bug_step3_triage_LLM.prompt +95 -0
  70. pdd/prompts/agentic_bug_step4_reproduce_LLM.prompt +97 -0
  71. pdd/prompts/agentic_bug_step5_root_cause_LLM.prompt +123 -0
  72. pdd/prompts/agentic_bug_step6_test_plan_LLM.prompt +107 -0
  73. pdd/prompts/agentic_bug_step7_generate_LLM.prompt +172 -0
  74. pdd/prompts/agentic_bug_step8_verify_LLM.prompt +119 -0
  75. pdd/prompts/agentic_bug_step9_e2e_test_LLM.prompt +289 -0
  76. pdd/prompts/agentic_change_step10_identify_issues_LLM.prompt +1006 -0
  77. pdd/prompts/agentic_change_step11_fix_issues_LLM.prompt +984 -0
  78. pdd/prompts/agentic_change_step12_create_pr_LLM.prompt +131 -0
  79. pdd/prompts/agentic_change_step1_duplicate_LLM.prompt +73 -0
  80. pdd/prompts/agentic_change_step2_docs_LLM.prompt +101 -0
  81. pdd/prompts/agentic_change_step3_research_LLM.prompt +126 -0
  82. pdd/prompts/agentic_change_step4_clarify_LLM.prompt +164 -0
  83. pdd/prompts/agentic_change_step5_docs_change_LLM.prompt +981 -0
  84. pdd/prompts/agentic_change_step6_devunits_LLM.prompt +1005 -0
  85. pdd/prompts/agentic_change_step7_architecture_LLM.prompt +1044 -0
  86. pdd/prompts/agentic_change_step8_analyze_LLM.prompt +1027 -0
  87. pdd/prompts/agentic_change_step9_implement_LLM.prompt +1077 -0
  88. pdd/prompts/agentic_e2e_fix_step1_unit_tests_LLM.prompt +90 -0
  89. pdd/prompts/agentic_e2e_fix_step2_e2e_tests_LLM.prompt +91 -0
  90. pdd/prompts/agentic_e2e_fix_step3_root_cause_LLM.prompt +89 -0
  91. pdd/prompts/agentic_e2e_fix_step4_fix_e2e_tests_LLM.prompt +96 -0
  92. pdd/prompts/agentic_e2e_fix_step5_identify_devunits_LLM.prompt +91 -0
  93. pdd/prompts/agentic_e2e_fix_step6_create_unit_tests_LLM.prompt +106 -0
  94. pdd/prompts/agentic_e2e_fix_step7_verify_tests_LLM.prompt +116 -0
  95. pdd/prompts/agentic_e2e_fix_step8_run_pdd_fix_LLM.prompt +120 -0
  96. pdd/prompts/agentic_e2e_fix_step9_verify_all_LLM.prompt +146 -0
  97. pdd/prompts/agentic_fix_primary_LLM.prompt +2 -2
  98. pdd/prompts/agentic_update_LLM.prompt +192 -338
  99. pdd/prompts/auto_include_LLM.prompt +22 -0
  100. pdd/prompts/change_LLM.prompt +3093 -1
  101. pdd/prompts/detect_change_LLM.prompt +571 -14
  102. pdd/prompts/fix_code_module_errors_LLM.prompt +8 -0
  103. pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +1 -0
  104. pdd/prompts/generate_test_LLM.prompt +20 -1
  105. pdd/prompts/generate_test_from_example_LLM.prompt +115 -0
  106. pdd/prompts/insert_includes_LLM.prompt +262 -252
  107. pdd/prompts/prompt_code_diff_LLM.prompt +119 -0
  108. pdd/prompts/prompt_diff_LLM.prompt +82 -0
  109. pdd/remote_session.py +876 -0
  110. pdd/server/__init__.py +52 -0
  111. pdd/server/app.py +335 -0
  112. pdd/server/click_executor.py +587 -0
  113. pdd/server/executor.py +338 -0
  114. pdd/server/jobs.py +661 -0
  115. pdd/server/models.py +241 -0
  116. pdd/server/routes/__init__.py +31 -0
  117. pdd/server/routes/architecture.py +451 -0
  118. pdd/server/routes/auth.py +364 -0
  119. pdd/server/routes/commands.py +929 -0
  120. pdd/server/routes/config.py +42 -0
  121. pdd/server/routes/files.py +603 -0
  122. pdd/server/routes/prompts.py +1322 -0
  123. pdd/server/routes/websocket.py +473 -0
  124. pdd/server/security.py +243 -0
  125. pdd/server/terminal_spawner.py +209 -0
  126. pdd/server/token_counter.py +222 -0
  127. pdd/summarize_directory.py +236 -237
  128. pdd/sync_animation.py +8 -4
  129. pdd/sync_determine_operation.py +329 -47
  130. pdd/sync_main.py +272 -28
  131. pdd/sync_orchestration.py +136 -75
  132. pdd/template_expander.py +161 -0
  133. pdd/templates/architecture/architecture_json.prompt +41 -46
  134. pdd/trace.py +1 -1
  135. pdd/track_cost.py +0 -13
  136. pdd/unfinished_prompt.py +2 -1
  137. pdd/update_main.py +23 -5
  138. {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.118.dist-info}/METADATA +15 -10
  139. pdd_cli-0.0.118.dist-info/RECORD +227 -0
  140. pdd_cli-0.0.90.dist-info/RECORD +0 -153
  141. {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.118.dist-info}/WHEEL +0 -0
  142. {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.118.dist-info}/entry_points.txt +0 -0
  143. {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.118.dist-info}/licenses/LICENSE +0 -0
  144. {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.118.dist-info}/top_level.txt +0 -0
pdd/server/__init__.py ADDED
@@ -0,0 +1,52 @@
1
+ """
2
+ PDD Server Package.
3
+
4
+ This package provides the REST API server, job management, and command execution
5
+ infrastructure for the PDD tool. It enables the web frontend to interact with
6
+ the local project environment securely.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from .app import create_app, run_server
12
+ from .executor import execute_pdd_command
13
+ from .jobs import Job, JobManager
14
+ from .models import ServerConfig, ServerStatus
15
+ from .routes.websocket import ConnectionManager
16
+ from .security import PathValidator, SecurityError
17
+
18
+ # Global Constants
19
+ DEFAULT_HOST = "127.0.0.1"
20
+ DEFAULT_PORT = 9876
21
+ API_VERSION = "v1"
22
+
23
+ __version__ = "0.1.0"
24
+
25
+ __all__ = [
26
+ # App
27
+ "create_app",
28
+ "run_server",
29
+
30
+ # Models
31
+ "ServerConfig",
32
+ "ServerStatus",
33
+
34
+ # Jobs
35
+ "Job",
36
+ "JobManager",
37
+
38
+ # Security
39
+ "PathValidator",
40
+ "SecurityError",
41
+
42
+ # Websockets
43
+ "ConnectionManager",
44
+
45
+ # Executor
46
+ "execute_pdd_command",
47
+
48
+ # Constants
49
+ "DEFAULT_HOST",
50
+ "DEFAULT_PORT",
51
+ "API_VERSION",
52
+ ]
pdd/server/app.py ADDED
@@ -0,0 +1,335 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from contextlib import asynccontextmanager
5
+ from datetime import datetime, timezone
6
+ from pathlib import Path
7
+ from typing import Optional, List, Union
8
+
9
+ import uvicorn
10
+ from fastapi import FastAPI, Request, status
11
+ from fastapi.responses import JSONResponse, FileResponse, HTMLResponse
12
+ from fastapi.exceptions import RequestValidationError
13
+ from fastapi.staticfiles import StaticFiles
14
+ from rich.console import Console
15
+
16
+ from .models import ServerStatus, ServerConfig
17
+ from .security import (
18
+ PathValidator,
19
+ SecurityError,
20
+ configure_cors,
21
+ SecurityLoggingMiddleware,
22
+ )
23
+ from .jobs import JobManager
24
+ from .routes.websocket import ConnectionManager, create_websocket_routes
25
+ from .routes import architecture, auth, files, commands, prompts
26
+ from .routes import websocket as ws_routes
27
+ from .routes.config import router as config_router
28
+
29
+ # Initialize Rich console
30
+ console = Console()
31
+
32
+ # ============================================================================
33
+ # Application State
34
+ # ============================================================================
35
+
36
+ class AppState:
37
+ """
38
+ Application state container for dependency injection.
39
+ Holds thread-safe references to shared managers and configuration.
40
+ """
41
+
42
+ def __init__(self, project_root: Path, config: Optional[ServerConfig] = None):
43
+ self.project_root = project_root.resolve()
44
+ self.start_time = datetime.now(timezone.utc)
45
+ self.version = "0.1.0" # In a real app, load from package metadata
46
+
47
+ # Store server config for port access
48
+ self.config = config or ServerConfig()
49
+
50
+ # Initialize managers
51
+ self.path_validator = PathValidator(self.project_root)
52
+ # SAFETY: Limit concurrent jobs to 3 - LLM calls are resource-intensive
53
+ # Running too many in parallel can exhaust memory/CPU and crash the system
54
+ self.job_manager = JobManager(max_concurrent=3, project_root=self.project_root)
55
+ self.connection_manager = ConnectionManager()
56
+
57
+ @property
58
+ def server_port(self) -> int:
59
+ """Get the configured server port."""
60
+ return self.config.port
61
+
62
+ @property
63
+ def uptime_seconds(self) -> float:
64
+ return (datetime.now(timezone.utc) - self.start_time).total_seconds()
65
+
66
+
67
+ # Global state instance (set during app creation)
68
+ _app_state: Optional[AppState] = None
69
+
70
+
71
+ def get_app_state() -> AppState:
72
+ """Dependency to get the global application state."""
73
+ if _app_state is None:
74
+ raise RuntimeError("Application state not initialized. Call create_app() first.")
75
+ return _app_state
76
+
77
+
78
+ def get_path_validator() -> PathValidator:
79
+ """Dependency to get the path validator."""
80
+ return get_app_state().path_validator
81
+
82
+
83
+ def get_job_manager() -> JobManager:
84
+ """Dependency to get the job manager."""
85
+ return get_app_state().job_manager
86
+
87
+
88
+ def get_connection_manager() -> ConnectionManager:
89
+ """Dependency to get the WebSocket connection manager."""
90
+ return get_app_state().connection_manager
91
+
92
+
93
+ def get_server_port() -> int:
94
+ """Dependency to get the configured server port."""
95
+ return get_app_state().server_port
96
+
97
+
98
+ # ============================================================================
99
+ # Exception Handlers
100
+ # ============================================================================
101
+
102
+ async def security_exception_handler(request: Request, exc: SecurityError):
103
+ """Handle security violations (403)."""
104
+ # Log the full error with code for server-side debugging
105
+ console.print(f"[bold red]Security Violation:[/bold red] {exc.message} ({exc.code})")
106
+
107
+ # Return only the message to the client to match expected log output behavior
108
+ return JSONResponse(
109
+ status_code=status.HTTP_403_FORBIDDEN,
110
+ content={"detail": exc.message},
111
+ )
112
+
113
+
114
+ async def validation_exception_handler(request: Request, exc: RequestValidationError):
115
+ """Handle Pydantic validation errors (422)."""
116
+ return JSONResponse(
117
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
118
+ content={"detail": exc.errors(), "body": str(exc.body)},
119
+ )
120
+
121
+
122
+ async def generic_exception_handler(request: Request, exc: Exception):
123
+ """Handle unexpected exceptions (500)."""
124
+ console.print(f"[bold red]Server Error:[/bold red] {str(exc)}")
125
+ return JSONResponse(
126
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
127
+ content={"detail": "Internal server error"},
128
+ )
129
+
130
+
131
+ # ============================================================================
132
+ # App Factory & Lifespan
133
+ # ============================================================================
134
+
135
+ @asynccontextmanager
136
+ async def lifespan(app: FastAPI):
137
+ """
138
+ Application lifespan manager.
139
+ Handles startup initialization and shutdown cleanup.
140
+ """
141
+ state = get_app_state()
142
+
143
+ # Startup
144
+ console.print(f"[green]PDD Server starting...[/green]")
145
+ console.print(f"Project Root: [bold]{state.project_root}[/bold]")
146
+
147
+ # Start remote session heartbeat and command polling if configured
148
+ from ..remote_session import get_active_session_manager
149
+ session_manager = get_active_session_manager()
150
+ if session_manager:
151
+ session_manager.start_heartbeat()
152
+ session_manager.start_command_polling()
153
+ console.print("[dim]Remote session heartbeat and command polling started[/dim]")
154
+
155
+ yield
156
+
157
+ # Shutdown
158
+ console.print("[yellow]Shutting down PDD Server...[/yellow]")
159
+
160
+ # Stop remote session heartbeat and command polling
161
+ if session_manager:
162
+ try:
163
+ await session_manager.stop_heartbeat()
164
+ await session_manager.stop_command_polling()
165
+ console.print("[dim]Remote session heartbeat and command polling stopped[/dim]")
166
+ except Exception as e:
167
+ console.print(f"[yellow]Warning: Error stopping remote session tasks: {e}[/yellow]")
168
+
169
+ # Cancel active jobs
170
+ try:
171
+ active_jobs = state.job_manager.get_active_jobs()
172
+ if active_jobs:
173
+ console.print(f"Cancelling {len(active_jobs)} active jobs...")
174
+ await state.job_manager.shutdown()
175
+ except Exception as e:
176
+ console.print(f"[red]Error during job manager shutdown: {e}[/red]")
177
+
178
+ console.print("[green]Shutdown complete.[/green]")
179
+
180
+
181
+ def create_app(
182
+ project_root: Path,
183
+ config: Optional[ServerConfig] = None,
184
+ allowed_origins: Optional[List[str]] = None
185
+ ) -> FastAPI:
186
+ """
187
+ Create and configure the FastAPI application.
188
+
189
+ Args:
190
+ project_root: The project directory to serve.
191
+ config: Server configuration object (preferred).
192
+ allowed_origins: List of allowed CORS origins (legacy/fallback).
193
+
194
+ Returns:
195
+ Configured FastAPI application.
196
+ """
197
+ global _app_state
198
+ _app_state = AppState(project_root, config=config)
199
+
200
+ # Determine configuration with proper fallback
201
+ origins = None
202
+ if config:
203
+ origins = config.allowed_origins
204
+
205
+ if origins is None:
206
+ origins = allowed_origins
207
+
208
+ app = FastAPI(
209
+ title="PDD Server",
210
+ description="Local REST server for Prompt Driven Development (PDD) web frontend",
211
+ version=_app_state.version,
212
+ lifespan=lifespan,
213
+ docs_url="/docs",
214
+ redoc_url="/redoc",
215
+ )
216
+
217
+ # 1. Configure Middleware
218
+ app.add_middleware(SecurityLoggingMiddleware)
219
+ configure_cors(app, origins)
220
+
221
+ # 2. Register Exception Handlers
222
+ app.add_exception_handler(SecurityError, security_exception_handler)
223
+ app.add_exception_handler(RequestValidationError, validation_exception_handler)
224
+ app.add_exception_handler(Exception, generic_exception_handler)
225
+
226
+ # 3. Register Routes
227
+ @app.get("/api/v1/status", response_model=ServerStatus, tags=["status"])
228
+ async def get_status():
229
+ state = get_app_state()
230
+ return ServerStatus(
231
+ version=state.version,
232
+ project_root=str(state.project_root),
233
+ uptime_seconds=state.uptime_seconds,
234
+ active_jobs=len(state.job_manager.get_active_jobs()),
235
+ connected_clients=len(state.connection_manager.active_connections),
236
+ )
237
+
238
+ app.dependency_overrides[files.get_path_validator] = get_path_validator
239
+ app.dependency_overrides[commands.get_job_manager] = get_job_manager
240
+ app.dependency_overrides[commands.get_project_root] = lambda: get_app_state().project_root
241
+ app.dependency_overrides[commands.get_server_port] = get_server_port
242
+ app.dependency_overrides[ws_routes.get_job_manager] = get_job_manager
243
+ app.dependency_overrides[ws_routes.get_project_root] = lambda: get_app_state().project_root
244
+ app.dependency_overrides[prompts.get_path_validator] = get_path_validator
245
+
246
+ app.include_router(architecture.router)
247
+ app.include_router(auth.router)
248
+ app.include_router(config_router)
249
+ app.include_router(files.router)
250
+ app.include_router(commands.router)
251
+ app.include_router(prompts.router)
252
+
253
+ create_websocket_routes(app, _app_state.connection_manager, _app_state.job_manager)
254
+
255
+ # 4. Serve Frontend Static Files
256
+ # Look for frontend dist in the pdd package directory
257
+ frontend_dist = Path(__file__).parent.parent / "frontend" / "dist"
258
+ if frontend_dist.exists():
259
+ console.print(f"[green]Serving frontend from:[/green] {frontend_dist}")
260
+
261
+ # Serve static assets (JS, CSS, etc.)
262
+ app.mount("/assets", StaticFiles(directory=frontend_dist / "assets"), name="assets")
263
+
264
+ # Serve index.html for the root and any non-API routes (SPA fallback)
265
+ @app.get("/", response_class=HTMLResponse)
266
+ async def serve_frontend():
267
+ index_file = frontend_dist / "index.html"
268
+ if index_file.exists():
269
+ return FileResponse(index_file)
270
+ return HTMLResponse("<h1>Frontend not found</h1>", status_code=404)
271
+
272
+ # Catch-all for SPA routing (must be last)
273
+ @app.get("/{path:path}")
274
+ async def serve_spa_fallback(path: str):
275
+ # Don't intercept API, docs, or WebSocket routes
276
+ if path.startswith(("api/", "docs", "redoc", "openapi.json", "ws/")):
277
+ return JSONResponse({"detail": "Not found"}, status_code=404)
278
+
279
+ # Try to serve the file directly first
280
+ file_path = frontend_dist / path
281
+ if file_path.exists() and file_path.is_file():
282
+ return FileResponse(file_path)
283
+
284
+ # Fall back to index.html for SPA routing
285
+ index_file = frontend_dist / "index.html"
286
+ if index_file.exists():
287
+ return FileResponse(index_file)
288
+ return JSONResponse({"detail": "Not found"}, status_code=404)
289
+ else:
290
+ console.print(f"[yellow]Frontend not found at {frontend_dist}[/yellow]")
291
+ console.print("[yellow]Run 'npm run build' in pdd/frontend to build the frontend[/yellow]")
292
+
293
+ return app
294
+
295
+
296
+ # ============================================================================
297
+ # Server Runner
298
+ # ============================================================================
299
+
300
+ def run_server(
301
+ project_root: Optional[Path] = None,
302
+ host: str = "127.0.0.1",
303
+ port: int = 9876,
304
+ log_level: str = "info",
305
+ allowed_origins: Optional[List[str]] = None,
306
+ app: Optional[FastAPI] = None,
307
+ config: Optional[ServerConfig] = None
308
+ ) -> None:
309
+ """
310
+ Run the PDD server using Uvicorn.
311
+ """
312
+ if config:
313
+ final_host = config.host
314
+ final_port = config.port
315
+ final_log_level = config.log_level
316
+ else:
317
+ final_host = host
318
+ final_port = port
319
+ final_log_level = log_level
320
+
321
+ if app is None:
322
+ if project_root is None:
323
+ raise ValueError("Must provide either 'app' or 'project_root'.")
324
+ app = create_app(project_root, config=config, allowed_origins=allowed_origins)
325
+
326
+ console.print(f"[bold green]PDD Server running on http://{final_host}:{final_port}[/bold green]")
327
+ console.print(f"API Documentation: http://{final_host}:{final_port}/docs")
328
+
329
+ uvicorn.run(
330
+ app,
331
+ host=final_host,
332
+ port=final_port,
333
+ log_level=final_log_level,
334
+ access_log=False,
335
+ )