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.
- pdd/__init__.py +38 -6
- pdd/agentic_bug.py +323 -0
- pdd/agentic_bug_orchestrator.py +506 -0
- pdd/agentic_change.py +231 -0
- pdd/agentic_change_orchestrator.py +537 -0
- pdd/agentic_common.py +533 -770
- pdd/agentic_crash.py +2 -1
- pdd/agentic_e2e_fix.py +319 -0
- pdd/agentic_e2e_fix_orchestrator.py +582 -0
- pdd/agentic_fix.py +118 -3
- pdd/agentic_update.py +27 -9
- pdd/agentic_verify.py +3 -2
- pdd/architecture_sync.py +565 -0
- pdd/auth_service.py +210 -0
- pdd/auto_deps_main.py +63 -53
- pdd/auto_include.py +236 -3
- pdd/auto_update.py +125 -47
- pdd/bug_main.py +195 -23
- pdd/cmd_test_main.py +345 -197
- pdd/code_generator.py +4 -2
- pdd/code_generator_main.py +118 -32
- pdd/commands/__init__.py +6 -0
- pdd/commands/analysis.py +113 -48
- pdd/commands/auth.py +309 -0
- pdd/commands/connect.py +358 -0
- pdd/commands/fix.py +155 -114
- pdd/commands/generate.py +5 -0
- pdd/commands/maintenance.py +3 -2
- pdd/commands/misc.py +8 -0
- pdd/commands/modify.py +225 -163
- pdd/commands/sessions.py +284 -0
- pdd/commands/utility.py +12 -7
- pdd/construct_paths.py +334 -32
- pdd/context_generator_main.py +167 -170
- pdd/continue_generation.py +6 -3
- pdd/core/__init__.py +33 -0
- pdd/core/cli.py +44 -7
- pdd/core/cloud.py +237 -0
- pdd/core/dump.py +68 -20
- pdd/core/errors.py +4 -0
- pdd/core/remote_session.py +61 -0
- pdd/crash_main.py +219 -23
- pdd/data/llm_model.csv +4 -4
- pdd/docs/prompting_guide.md +864 -0
- pdd/docs/whitepaper_with_benchmarks/data_and_functions/benchmark_analysis.py +495 -0
- pdd/docs/whitepaper_with_benchmarks/data_and_functions/creation_compare.py +528 -0
- pdd/fix_code_loop.py +208 -34
- pdd/fix_code_module_errors.py +6 -2
- pdd/fix_error_loop.py +291 -38
- pdd/fix_main.py +208 -6
- pdd/fix_verification_errors_loop.py +235 -26
- pdd/fix_verification_main.py +269 -83
- pdd/frontend/dist/assets/index-B5DZHykP.css +1 -0
- pdd/frontend/dist/assets/index-CUWd8al1.js +450 -0
- pdd/frontend/dist/index.html +376 -0
- pdd/frontend/dist/logo.svg +33 -0
- pdd/generate_output_paths.py +46 -5
- pdd/generate_test.py +212 -151
- pdd/get_comment.py +19 -44
- pdd/get_extension.py +8 -9
- pdd/get_jwt_token.py +309 -20
- pdd/get_language.py +8 -7
- pdd/get_run_command.py +7 -5
- pdd/insert_includes.py +2 -1
- pdd/llm_invoke.py +531 -97
- pdd/load_prompt_template.py +15 -34
- pdd/operation_log.py +342 -0
- pdd/path_resolution.py +140 -0
- pdd/postprocess.py +122 -97
- pdd/preprocess.py +68 -12
- pdd/preprocess_main.py +33 -1
- pdd/prompts/agentic_bug_step10_pr_LLM.prompt +182 -0
- pdd/prompts/agentic_bug_step1_duplicate_LLM.prompt +73 -0
- pdd/prompts/agentic_bug_step2_docs_LLM.prompt +129 -0
- pdd/prompts/agentic_bug_step3_triage_LLM.prompt +95 -0
- pdd/prompts/agentic_bug_step4_reproduce_LLM.prompt +97 -0
- pdd/prompts/agentic_bug_step5_root_cause_LLM.prompt +123 -0
- pdd/prompts/agentic_bug_step6_test_plan_LLM.prompt +107 -0
- pdd/prompts/agentic_bug_step7_generate_LLM.prompt +172 -0
- pdd/prompts/agentic_bug_step8_verify_LLM.prompt +119 -0
- pdd/prompts/agentic_bug_step9_e2e_test_LLM.prompt +289 -0
- pdd/prompts/agentic_change_step10_identify_issues_LLM.prompt +1006 -0
- pdd/prompts/agentic_change_step11_fix_issues_LLM.prompt +984 -0
- pdd/prompts/agentic_change_step12_create_pr_LLM.prompt +140 -0
- pdd/prompts/agentic_change_step1_duplicate_LLM.prompt +73 -0
- pdd/prompts/agentic_change_step2_docs_LLM.prompt +101 -0
- pdd/prompts/agentic_change_step3_research_LLM.prompt +126 -0
- pdd/prompts/agentic_change_step4_clarify_LLM.prompt +164 -0
- pdd/prompts/agentic_change_step5_docs_change_LLM.prompt +981 -0
- pdd/prompts/agentic_change_step6_devunits_LLM.prompt +1005 -0
- pdd/prompts/agentic_change_step7_architecture_LLM.prompt +1044 -0
- pdd/prompts/agentic_change_step8_analyze_LLM.prompt +1027 -0
- pdd/prompts/agentic_change_step9_implement_LLM.prompt +1077 -0
- pdd/prompts/agentic_e2e_fix_step1_unit_tests_LLM.prompt +90 -0
- pdd/prompts/agentic_e2e_fix_step2_e2e_tests_LLM.prompt +91 -0
- pdd/prompts/agentic_e2e_fix_step3_root_cause_LLM.prompt +89 -0
- pdd/prompts/agentic_e2e_fix_step4_fix_e2e_tests_LLM.prompt +96 -0
- pdd/prompts/agentic_e2e_fix_step5_identify_devunits_LLM.prompt +91 -0
- pdd/prompts/agentic_e2e_fix_step6_create_unit_tests_LLM.prompt +106 -0
- pdd/prompts/agentic_e2e_fix_step7_verify_tests_LLM.prompt +116 -0
- pdd/prompts/agentic_e2e_fix_step8_run_pdd_fix_LLM.prompt +120 -0
- pdd/prompts/agentic_e2e_fix_step9_verify_all_LLM.prompt +146 -0
- pdd/prompts/agentic_fix_primary_LLM.prompt +2 -2
- pdd/prompts/agentic_update_LLM.prompt +192 -338
- pdd/prompts/auto_include_LLM.prompt +22 -0
- pdd/prompts/change_LLM.prompt +3093 -1
- pdd/prompts/detect_change_LLM.prompt +571 -14
- pdd/prompts/fix_code_module_errors_LLM.prompt +8 -0
- pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +1 -0
- pdd/prompts/generate_test_LLM.prompt +19 -1
- pdd/prompts/generate_test_from_example_LLM.prompt +366 -0
- pdd/prompts/insert_includes_LLM.prompt +262 -252
- pdd/prompts/prompt_code_diff_LLM.prompt +123 -0
- pdd/prompts/prompt_diff_LLM.prompt +82 -0
- pdd/remote_session.py +876 -0
- pdd/server/__init__.py +52 -0
- pdd/server/app.py +335 -0
- pdd/server/click_executor.py +587 -0
- pdd/server/executor.py +338 -0
- pdd/server/jobs.py +661 -0
- pdd/server/models.py +241 -0
- pdd/server/routes/__init__.py +31 -0
- pdd/server/routes/architecture.py +451 -0
- pdd/server/routes/auth.py +364 -0
- pdd/server/routes/commands.py +929 -0
- pdd/server/routes/config.py +42 -0
- pdd/server/routes/files.py +603 -0
- pdd/server/routes/prompts.py +1347 -0
- pdd/server/routes/websocket.py +473 -0
- pdd/server/security.py +243 -0
- pdd/server/terminal_spawner.py +217 -0
- pdd/server/token_counter.py +222 -0
- pdd/summarize_directory.py +236 -237
- pdd/sync_animation.py +8 -4
- pdd/sync_determine_operation.py +329 -47
- pdd/sync_main.py +272 -28
- pdd/sync_orchestration.py +289 -211
- pdd/sync_order.py +304 -0
- pdd/template_expander.py +161 -0
- pdd/templates/architecture/architecture_json.prompt +41 -46
- pdd/trace.py +1 -1
- pdd/track_cost.py +0 -13
- pdd/unfinished_prompt.py +2 -1
- pdd/update_main.py +68 -26
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/METADATA +15 -10
- pdd_cli-0.0.121.dist-info/RECORD +229 -0
- pdd_cli-0.0.90.dist-info/RECORD +0 -153
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/licenses/LICENSE +0 -0
- {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
|
+
)
|