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,451 @@
1
+ """
2
+ REST API endpoints for architecture.json validation and sync operations.
3
+
4
+ Provides endpoints for:
5
+ - Validating architecture changes before saving
6
+ - Detecting circular dependencies, missing references, and structural issues
7
+ - Syncing architecture.json from prompt file metadata tags
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ from pathlib import Path
14
+ from typing import Any, Dict, List, Optional, Set
15
+
16
+ from fastapi import APIRouter
17
+ from pydantic import BaseModel, Field
18
+
19
+ from pdd.architecture_sync import (
20
+ ARCHITECTURE_JSON_PATH,
21
+ sync_all_prompts_to_architecture,
22
+ update_architecture_from_prompt,
23
+ get_architecture_entry_for_prompt,
24
+ generate_tags_from_architecture,
25
+ has_pdd_tags,
26
+ )
27
+
28
+
29
+ router = APIRouter(prefix="/api/v1/architecture", tags=["architecture"])
30
+
31
+
32
+ class ArchitectureModule(BaseModel):
33
+ """Schema for an architecture module."""
34
+
35
+ reason: str
36
+ description: str
37
+ dependencies: List[str]
38
+ priority: int
39
+ filename: str
40
+ filepath: str
41
+ tags: List[str] = Field(default_factory=list)
42
+ interface: Optional[Dict[str, Any]] = None
43
+
44
+
45
+ class ValidationError(BaseModel):
46
+ """Validation error that blocks saving."""
47
+
48
+ type: str # circular_dependency, missing_dependency, invalid_field
49
+ message: str
50
+ modules: List[str] # Affected module filenames
51
+
52
+
53
+ class ValidationWarning(BaseModel):
54
+ """Validation warning that is informational only."""
55
+
56
+ type: str # duplicate_dependency, orphan_module
57
+ message: str
58
+ modules: List[str]
59
+
60
+
61
+ class ValidateArchitectureRequest(BaseModel):
62
+ """Request body for architecture validation."""
63
+
64
+ modules: List[ArchitectureModule]
65
+
66
+
67
+ class ValidationResult(BaseModel):
68
+ """Result of architecture validation."""
69
+
70
+ valid: bool # True if no errors (warnings are OK)
71
+ errors: List[ValidationError]
72
+ warnings: List[ValidationWarning]
73
+
74
+
75
+ class SyncRequest(BaseModel):
76
+ """Request body for sync-from-prompts operation."""
77
+
78
+ filenames: Optional[List[str]] = None # None = sync all prompts
79
+ dry_run: bool = False
80
+
81
+
82
+ class SyncResult(BaseModel):
83
+ """Result of sync-from-prompts operation."""
84
+
85
+ success: bool
86
+ updated_count: int
87
+ skipped_count: int = 0
88
+ results: List[Dict[str, Any]]
89
+ validation: ValidationResult
90
+ errors: List[str] = Field(default_factory=list)
91
+
92
+
93
+ class GenerateTagsRequest(BaseModel):
94
+ """Request body for generate-tags-for-prompt operation."""
95
+
96
+ prompt_filename: str # e.g., "llm_invoke_python.prompt"
97
+
98
+
99
+ class GenerateTagsResult(BaseModel):
100
+ """Result of generate-tags-for-prompt operation."""
101
+
102
+ success: bool
103
+ tags: Optional[str] = None # Generated XML tags or None if not found
104
+ has_existing_tags: bool = False # True if prompt already has PDD tags
105
+ architecture_entry: Optional[Dict[str, Any]] = None # The full architecture entry
106
+ error: Optional[str] = None
107
+
108
+
109
+ def _detect_circular_dependencies(modules: List[ArchitectureModule]) -> List[List[str]]:
110
+ """
111
+ Detect circular dependencies using DFS with recursion stack.
112
+
113
+ Returns a list of cycles, where each cycle is a list of module filenames.
114
+ """
115
+ # Build adjacency graph: module -> list of modules it depends on
116
+ graph: Dict[str, Set[str]] = {}
117
+ all_filenames: Set[str] = set()
118
+
119
+ for module in modules:
120
+ all_filenames.add(module.filename)
121
+ graph[module.filename] = set(module.dependencies)
122
+
123
+ cycles: List[List[str]] = []
124
+ visited: Set[str] = set()
125
+ rec_stack: Set[str] = set()
126
+
127
+ def dfs(node: str, path: List[str]) -> None:
128
+ """DFS to detect cycles."""
129
+ if node in rec_stack:
130
+ # Found cycle - extract the cycle from path
131
+ try:
132
+ cycle_start = path.index(node)
133
+ cycle = path[cycle_start:] + [node]
134
+ cycles.append(cycle)
135
+ except ValueError:
136
+ pass
137
+ return
138
+
139
+ if node in visited or node not in graph:
140
+ return
141
+
142
+ visited.add(node)
143
+ rec_stack.add(node)
144
+ path.append(node)
145
+
146
+ for dep in graph.get(node, set()):
147
+ if dep in all_filenames: # Only follow edges to known modules
148
+ dfs(dep, path)
149
+
150
+ path.pop()
151
+ rec_stack.remove(node)
152
+
153
+ # Run DFS from each unvisited node
154
+ for filename in all_filenames:
155
+ if filename not in visited:
156
+ dfs(filename, [])
157
+
158
+ return cycles
159
+
160
+
161
+ def _validate_architecture(modules: List[ArchitectureModule]) -> ValidationResult:
162
+ """Validate architecture and return errors and warnings."""
163
+ errors: List[ValidationError] = []
164
+ warnings: List[ValidationWarning] = []
165
+
166
+ # Build set of all filenames
167
+ all_filenames = {m.filename for m in modules}
168
+
169
+ # Check for circular dependencies
170
+ cycles = _detect_circular_dependencies(modules)
171
+ for cycle in cycles:
172
+ errors.append(
173
+ ValidationError(
174
+ type="circular_dependency",
175
+ message=f"Circular dependency detected: {' -> '.join(cycle)}",
176
+ modules=cycle,
177
+ )
178
+ )
179
+
180
+ # Check for missing dependencies
181
+ for module in modules:
182
+ for dep in module.dependencies:
183
+ if dep not in all_filenames:
184
+ errors.append(
185
+ ValidationError(
186
+ type="missing_dependency",
187
+ message=f"Module '{module.filename}' depends on "
188
+ f"non-existent module '{dep}'",
189
+ modules=[module.filename, dep],
190
+ )
191
+ )
192
+
193
+ # Check for invalid/missing required fields
194
+ for module in modules:
195
+ if not module.filename or not module.filename.strip():
196
+ errors.append(
197
+ ValidationError(
198
+ type="invalid_field",
199
+ message="Module has empty filename",
200
+ modules=[module.filename or "(unnamed)"],
201
+ )
202
+ )
203
+ if not module.filepath or not module.filepath.strip():
204
+ errors.append(
205
+ ValidationError(
206
+ type="invalid_field",
207
+ message=f"Module '{module.filename}' has empty filepath",
208
+ modules=[module.filename],
209
+ )
210
+ )
211
+ if not module.description or not module.description.strip():
212
+ errors.append(
213
+ ValidationError(
214
+ type="invalid_field",
215
+ message=f"Module '{module.filename}' has empty description",
216
+ modules=[module.filename],
217
+ )
218
+ )
219
+
220
+ # Check for duplicate dependencies (warning)
221
+ for module in modules:
222
+ if len(module.dependencies) != len(set(module.dependencies)):
223
+ # Find the duplicates
224
+ seen: Set[str] = set()
225
+ duplicates: List[str] = []
226
+ for dep in module.dependencies:
227
+ if dep in seen:
228
+ duplicates.append(dep)
229
+ seen.add(dep)
230
+ warnings.append(
231
+ ValidationWarning(
232
+ type="duplicate_dependency",
233
+ message=f"Module '{module.filename}' has duplicate dependencies: "
234
+ f"{', '.join(duplicates)}",
235
+ modules=[module.filename],
236
+ )
237
+ )
238
+
239
+ # Check for orphan modules (warning)
240
+ # Build set of modules that are depended upon
241
+ depended_upon: Set[str] = set()
242
+ for module in modules:
243
+ depended_upon.update(module.dependencies)
244
+
245
+ for module in modules:
246
+ if not module.dependencies and module.filename not in depended_upon:
247
+ warnings.append(
248
+ ValidationWarning(
249
+ type="orphan_module",
250
+ message=f"Module '{module.filename}' has no dependencies "
251
+ f"and is not depended upon by any other module",
252
+ modules=[module.filename],
253
+ )
254
+ )
255
+
256
+ return ValidationResult(
257
+ valid=len(errors) == 0,
258
+ errors=errors,
259
+ warnings=warnings,
260
+ )
261
+
262
+
263
+ @router.post("/validate", response_model=ValidationResult)
264
+ async def validate_architecture(request: ValidateArchitectureRequest) -> ValidationResult:
265
+ """
266
+ Validate architecture for structural issues.
267
+
268
+ Checks for:
269
+ - Circular dependencies (error)
270
+ - Missing dependencies (error)
271
+ - Invalid/missing required fields (error)
272
+ - Duplicate dependencies (warning)
273
+ - Orphan modules (warning)
274
+
275
+ Returns validation result with valid flag, errors, and warnings.
276
+ Errors block saving (valid=False), warnings are informational (valid=True).
277
+ """
278
+ return _validate_architecture(request.modules)
279
+
280
+
281
+ @router.post("/sync-from-prompts", response_model=SyncResult)
282
+ async def sync_from_prompts(request: SyncRequest) -> SyncResult:
283
+ """
284
+ Sync architecture.json from prompt file metadata tags.
285
+
286
+ This endpoint reads PDD metadata tags (<pdd-reason>, <pdd-interface>,
287
+ <pdd-dependency>) from prompt files and updates the corresponding entries
288
+ in architecture.json.
289
+
290
+ Prompts are the source of truth - tags in prompts override architecture.json.
291
+ Validation is lenient - missing tags are OK, only updates fields with tags.
292
+
293
+ Request body:
294
+ {
295
+ "filenames": ["llm_invoke_python.prompt", ...] | null,
296
+ "dry_run": false
297
+ }
298
+
299
+ If filenames is null, syncs ALL prompt files.
300
+ If dry_run is true, validates changes without writing.
301
+
302
+ Returns:
303
+ {
304
+ "success": bool, // True if no errors and validation passed
305
+ "updated_count": int, // Number of modules updated
306
+ "skipped_count": int, // Number of modules skipped (no prompt file)
307
+ "results": [
308
+ {
309
+ "filename": "...",
310
+ "success": bool,
311
+ "updated": bool,
312
+ "changes": {"reason": {"old": ..., "new": ...}, ...}
313
+ },
314
+ ...
315
+ ],
316
+ "validation": {
317
+ "valid": bool,
318
+ "errors": [...], // Circular deps, missing deps, etc.
319
+ "warnings": [...] // Duplicates, orphans, etc.
320
+ },
321
+ "errors": [str, ...] // Sync operation errors
322
+ }
323
+ """
324
+ try:
325
+ # Perform sync operation
326
+ if request.filenames is None:
327
+ # Sync all prompts
328
+ sync_result = sync_all_prompts_to_architecture(dry_run=request.dry_run)
329
+ else:
330
+ # Sync specific prompts
331
+ results = []
332
+ updated_count = 0
333
+ errors_list = []
334
+
335
+ for filename in request.filenames:
336
+ result = update_architecture_from_prompt(filename, dry_run=request.dry_run)
337
+ results.append({
338
+ 'filename': filename,
339
+ 'success': result['success'],
340
+ 'updated': result['updated'],
341
+ 'changes': result['changes'],
342
+ 'error': result.get('error')
343
+ })
344
+
345
+ if result['success'] and result['updated']:
346
+ updated_count += 1
347
+ elif not result['success']:
348
+ errors_list.append(f"{filename}: {result['error']}")
349
+
350
+ sync_result = {
351
+ 'success': len(errors_list) == 0,
352
+ 'updated_count': updated_count,
353
+ 'skipped_count': 0,
354
+ 'results': results,
355
+ 'errors': errors_list
356
+ }
357
+
358
+ # Load updated architecture and validate
359
+ arch_path = Path(ARCHITECTURE_JSON_PATH)
360
+ arch_data = json.loads(arch_path.read_text(encoding='utf-8'))
361
+ modules = [ArchitectureModule(**mod) for mod in arch_data]
362
+ validation_result = _validate_architecture(modules)
363
+
364
+ # Overall success: sync succeeded AND validation passed
365
+ overall_success = sync_result['success'] and validation_result.valid
366
+
367
+ return SyncResult(
368
+ success=overall_success,
369
+ updated_count=sync_result['updated_count'],
370
+ skipped_count=sync_result.get('skipped_count', 0),
371
+ results=sync_result['results'],
372
+ validation=validation_result,
373
+ errors=sync_result.get('errors', [])
374
+ )
375
+
376
+ except Exception as e:
377
+ # Return error result
378
+ return SyncResult(
379
+ success=False,
380
+ updated_count=0,
381
+ skipped_count=0,
382
+ results=[],
383
+ validation=ValidationResult(valid=True, errors=[], warnings=[]),
384
+ errors=[f"Unexpected error: {str(e)}"]
385
+ )
386
+
387
+
388
+ @router.post("/generate-tags-for-prompt", response_model=GenerateTagsResult)
389
+ async def generate_tags_for_prompt(request: GenerateTagsRequest) -> GenerateTagsResult:
390
+ """
391
+ Generate PDD metadata tags for a prompt from architecture.json.
392
+
393
+ This is the reverse direction of sync-from-prompts: it reads the architecture.json
394
+ entry for a prompt and generates XML tags (<pdd-reason>, <pdd-interface>,
395
+ <pdd-dependency>) that can be injected into the prompt file.
396
+
397
+ Request body:
398
+ {
399
+ "prompt_filename": "llm_invoke_python.prompt"
400
+ }
401
+
402
+ Returns:
403
+ {
404
+ "success": bool,
405
+ "tags": "<pdd-reason>...</pdd-reason>\\n<pdd-interface>...</pdd-interface>\\n...",
406
+ "has_existing_tags": false, // True if prompt already has PDD tags
407
+ "architecture_entry": {...}, // The full architecture entry (for preview)
408
+ "error": "Error message if failed"
409
+ }
410
+ """
411
+ try:
412
+ # Get architecture entry for this prompt
413
+ entry = get_architecture_entry_for_prompt(request.prompt_filename)
414
+
415
+ if entry is None:
416
+ return GenerateTagsResult(
417
+ success=False,
418
+ tags=None,
419
+ has_existing_tags=False,
420
+ architecture_entry=None,
421
+ error=f"No architecture entry found for '{request.prompt_filename}'"
422
+ )
423
+
424
+ # Check if the prompt file already has PDD tags
425
+ prompts_dir = Path.cwd() / "prompts"
426
+ prompt_path = prompts_dir / request.prompt_filename
427
+ existing_tags = False
428
+
429
+ if prompt_path.exists():
430
+ prompt_content = prompt_path.read_text(encoding='utf-8')
431
+ existing_tags = has_pdd_tags(prompt_content)
432
+
433
+ # Generate tags from architecture entry
434
+ tags = generate_tags_from_architecture(entry)
435
+
436
+ return GenerateTagsResult(
437
+ success=True,
438
+ tags=tags if tags else None,
439
+ has_existing_tags=existing_tags,
440
+ architecture_entry=entry,
441
+ error=None
442
+ )
443
+
444
+ except Exception as e:
445
+ return GenerateTagsResult(
446
+ success=False,
447
+ tags=None,
448
+ has_existing_tags=False,
449
+ architecture_entry=None,
450
+ error=f"Error generating tags: {str(e)}"
451
+ )