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.
- pdd/__init__.py +40 -8
- pdd/agentic_bug.py +323 -0
- pdd/agentic_bug_orchestrator.py +497 -0
- pdd/agentic_change.py +231 -0
- pdd/agentic_change_orchestrator.py +526 -0
- pdd/agentic_common.py +598 -0
- pdd/agentic_crash.py +534 -0
- pdd/agentic_e2e_fix.py +319 -0
- pdd/agentic_e2e_fix_orchestrator.py +426 -0
- pdd/agentic_fix.py +1294 -0
- pdd/agentic_langtest.py +162 -0
- pdd/agentic_update.py +387 -0
- pdd/agentic_verify.py +183 -0
- pdd/architecture_sync.py +565 -0
- pdd/auth_service.py +210 -0
- pdd/auto_deps_main.py +71 -51
- pdd/auto_include.py +245 -5
- pdd/auto_update.py +125 -47
- pdd/bug_main.py +196 -23
- pdd/bug_to_unit_test.py +2 -0
- pdd/change_main.py +11 -4
- pdd/cli.py +22 -1181
- pdd/cmd_test_main.py +350 -150
- pdd/code_generator.py +60 -18
- pdd/code_generator_main.py +790 -57
- pdd/commands/__init__.py +48 -0
- pdd/commands/analysis.py +306 -0
- pdd/commands/auth.py +309 -0
- pdd/commands/connect.py +290 -0
- pdd/commands/fix.py +163 -0
- pdd/commands/generate.py +257 -0
- pdd/commands/maintenance.py +175 -0
- pdd/commands/misc.py +87 -0
- pdd/commands/modify.py +256 -0
- pdd/commands/report.py +144 -0
- pdd/commands/sessions.py +284 -0
- pdd/commands/templates.py +215 -0
- pdd/commands/utility.py +110 -0
- pdd/config_resolution.py +58 -0
- pdd/conflicts_main.py +8 -3
- pdd/construct_paths.py +589 -111
- pdd/context_generator.py +10 -2
- pdd/context_generator_main.py +175 -76
- pdd/continue_generation.py +53 -10
- pdd/core/__init__.py +33 -0
- pdd/core/cli.py +527 -0
- pdd/core/cloud.py +237 -0
- pdd/core/dump.py +554 -0
- pdd/core/errors.py +67 -0
- pdd/core/remote_session.py +61 -0
- pdd/core/utils.py +90 -0
- pdd/crash_main.py +262 -33
- pdd/data/language_format.csv +71 -63
- pdd/data/llm_model.csv +20 -18
- pdd/detect_change_main.py +5 -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 +523 -95
- pdd/fix_code_module_errors.py +6 -2
- pdd/fix_error_loop.py +491 -92
- pdd/fix_errors_from_unit_tests.py +4 -3
- pdd/fix_main.py +278 -21
- pdd/fix_verification_errors.py +12 -100
- pdd/fix_verification_errors_loop.py +529 -286
- pdd/fix_verification_main.py +294 -89
- pdd/frontend/dist/assets/index-B5DZHykP.css +1 -0
- pdd/frontend/dist/assets/index-DQ3wkeQ2.js +449 -0
- pdd/frontend/dist/index.html +376 -0
- pdd/frontend/dist/logo.svg +33 -0
- pdd/generate_output_paths.py +139 -15
- pdd/generate_test.py +218 -146
- pdd/get_comment.py +19 -44
- pdd/get_extension.py +8 -9
- pdd/get_jwt_token.py +318 -22
- pdd/get_language.py +8 -7
- pdd/get_run_command.py +75 -0
- pdd/get_test_command.py +68 -0
- pdd/git_update.py +70 -19
- pdd/incremental_code_generator.py +2 -2
- pdd/insert_includes.py +13 -4
- pdd/llm_invoke.py +1711 -181
- pdd/load_prompt_template.py +19 -12
- pdd/path_resolution.py +140 -0
- pdd/pdd_completion.fish +25 -2
- pdd/pdd_completion.sh +30 -4
- pdd/pdd_completion.zsh +79 -4
- pdd/postprocess.py +14 -4
- pdd/preprocess.py +293 -24
- pdd/preprocess_main.py +41 -6
- 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 +131 -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_crash_explore_LLM.prompt +49 -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_explore_LLM.prompt +45 -0
- pdd/prompts/agentic_fix_harvest_only_LLM.prompt +48 -0
- pdd/prompts/agentic_fix_primary_LLM.prompt +85 -0
- pdd/prompts/agentic_update_LLM.prompt +925 -0
- pdd/prompts/agentic_verify_explore_LLM.prompt +45 -0
- pdd/prompts/auto_include_LLM.prompt +122 -905
- pdd/prompts/change_LLM.prompt +3093 -1
- pdd/prompts/detect_change_LLM.prompt +686 -27
- pdd/prompts/example_generator_LLM.prompt +22 -1
- pdd/prompts/extract_code_LLM.prompt +5 -1
- pdd/prompts/extract_program_code_fix_LLM.prompt +7 -1
- pdd/prompts/extract_prompt_update_LLM.prompt +7 -8
- pdd/prompts/extract_promptline_LLM.prompt +17 -11
- pdd/prompts/find_verification_errors_LLM.prompt +6 -0
- pdd/prompts/fix_code_module_errors_LLM.prompt +12 -2
- pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +9 -0
- pdd/prompts/fix_verification_errors_LLM.prompt +22 -0
- pdd/prompts/generate_test_LLM.prompt +41 -7
- pdd/prompts/generate_test_from_example_LLM.prompt +115 -0
- pdd/prompts/increase_tests_LLM.prompt +1 -5
- pdd/prompts/insert_includes_LLM.prompt +316 -186
- pdd/prompts/prompt_code_diff_LLM.prompt +119 -0
- pdd/prompts/prompt_diff_LLM.prompt +82 -0
- pdd/prompts/trace_LLM.prompt +25 -22
- pdd/prompts/unfinished_prompt_LLM.prompt +85 -1
- pdd/prompts/update_prompt_LLM.prompt +22 -1
- pdd/pytest_output.py +127 -12
- pdd/remote_session.py +876 -0
- pdd/render_mermaid.py +236 -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 +1322 -0
- pdd/server/routes/websocket.py +473 -0
- pdd/server/security.py +243 -0
- pdd/server/terminal_spawner.py +209 -0
- pdd/server/token_counter.py +222 -0
- pdd/setup_tool.py +648 -0
- pdd/simple_math.py +2 -0
- pdd/split_main.py +3 -2
- pdd/summarize_directory.py +237 -195
- pdd/sync_animation.py +8 -4
- pdd/sync_determine_operation.py +839 -112
- pdd/sync_main.py +351 -57
- pdd/sync_orchestration.py +1400 -756
- pdd/sync_tui.py +848 -0
- pdd/template_expander.py +161 -0
- pdd/template_registry.py +264 -0
- pdd/templates/architecture/architecture_json.prompt +237 -0
- pdd/templates/generic/generate_prompt.prompt +174 -0
- pdd/trace.py +168 -12
- pdd/trace_main.py +4 -3
- pdd/track_cost.py +140 -63
- pdd/unfinished_prompt.py +51 -4
- pdd/update_main.py +567 -67
- pdd/update_model_costs.py +2 -2
- pdd/update_prompt.py +19 -4
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/METADATA +29 -11
- pdd_cli-0.0.118.dist-info/RECORD +227 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/licenses/LICENSE +1 -1
- pdd_cli-0.0.45.dist-info/RECORD +0 -116
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/top_level.txt +0 -0
pdd/template_expander.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# pdd/template_expander.py
|
|
2
|
+
"""
|
|
3
|
+
Template expansion utility for output path configuration.
|
|
4
|
+
|
|
5
|
+
This module provides a function to expand path templates with placeholders
|
|
6
|
+
like {name}, {category}, {ext}, etc. It enables extensible project layouts
|
|
7
|
+
for different languages and frameworks (Python, TypeScript, Vue, Go, etc.).
|
|
8
|
+
|
|
9
|
+
Supported placeholders:
|
|
10
|
+
{name} - Base name (last segment of input path)
|
|
11
|
+
{category} - Parent path segments (empty if none)
|
|
12
|
+
{dir_prefix} - Full input directory prefix with trailing /
|
|
13
|
+
{ext} - File extension from language (e.g., "py", "tsx")
|
|
14
|
+
{language} - Full language name (e.g., "python", "typescript")
|
|
15
|
+
{name_snake} - snake_case version of name
|
|
16
|
+
{name_pascal} - PascalCase version of name
|
|
17
|
+
{name_kebab} - kebab-case version of name
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
>>> expand_template(
|
|
21
|
+
... "frontend/src/components/{category}/{name}/{name}.tsx",
|
|
22
|
+
... {"name": "AssetCard", "category": "marketplace"}
|
|
23
|
+
... )
|
|
24
|
+
'frontend/src/components/marketplace/AssetCard/AssetCard.tsx'
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import re
|
|
28
|
+
import os
|
|
29
|
+
from typing import Dict, Any
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _to_snake_case(s: str) -> str:
|
|
33
|
+
"""
|
|
34
|
+
Convert string to snake_case.
|
|
35
|
+
|
|
36
|
+
Handles PascalCase, camelCase, and existing snake_case.
|
|
37
|
+
|
|
38
|
+
Examples:
|
|
39
|
+
AssetCard -> asset_card
|
|
40
|
+
assetCard -> asset_card
|
|
41
|
+
already_snake -> already_snake
|
|
42
|
+
"""
|
|
43
|
+
if not s:
|
|
44
|
+
return s
|
|
45
|
+
# Insert underscore before uppercase letters (except at start)
|
|
46
|
+
result = re.sub(r'(?<!^)(?=[A-Z])', '_', s)
|
|
47
|
+
return result.lower()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _to_pascal_case(s: str) -> str:
|
|
51
|
+
"""
|
|
52
|
+
Convert string to PascalCase.
|
|
53
|
+
|
|
54
|
+
Handles snake_case, kebab-case, and existing PascalCase.
|
|
55
|
+
|
|
56
|
+
Examples:
|
|
57
|
+
asset_card -> AssetCard
|
|
58
|
+
asset-card -> AssetCard
|
|
59
|
+
AssetCard -> Assetcard (note: re-capitalizes)
|
|
60
|
+
"""
|
|
61
|
+
if not s:
|
|
62
|
+
return s
|
|
63
|
+
# Split on underscores, hyphens, or other common delimiters
|
|
64
|
+
parts = re.split(r'[_\-\s]+', s)
|
|
65
|
+
return ''.join(part.title() for part in parts if part)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _to_kebab_case(s: str) -> str:
|
|
69
|
+
"""
|
|
70
|
+
Convert string to kebab-case.
|
|
71
|
+
|
|
72
|
+
Handles PascalCase, camelCase, and existing kebab-case.
|
|
73
|
+
|
|
74
|
+
Examples:
|
|
75
|
+
AssetCard -> asset-card
|
|
76
|
+
assetCard -> asset-card
|
|
77
|
+
already-kebab -> already-kebab
|
|
78
|
+
"""
|
|
79
|
+
if not s:
|
|
80
|
+
return s
|
|
81
|
+
# Insert hyphen before uppercase letters (except at start)
|
|
82
|
+
result = re.sub(r'(?<!^)(?=[A-Z])', '-', s)
|
|
83
|
+
return result.lower()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _normalize_path(path: str) -> str:
|
|
87
|
+
"""
|
|
88
|
+
Normalize a path to remove double slashes and resolve . and ..
|
|
89
|
+
|
|
90
|
+
This handles edge cases like empty {category} producing paths like:
|
|
91
|
+
"src/components//Button" -> "src/components/Button"
|
|
92
|
+
|
|
93
|
+
Unlike os.path.normpath, this preserves relative paths without
|
|
94
|
+
converting them to absolute paths.
|
|
95
|
+
"""
|
|
96
|
+
if not path:
|
|
97
|
+
return path
|
|
98
|
+
|
|
99
|
+
# Split path and filter empty segments (which cause double slashes)
|
|
100
|
+
parts = path.split('/')
|
|
101
|
+
normalized_parts = [p for p in parts if p]
|
|
102
|
+
|
|
103
|
+
# Rejoin with single slashes
|
|
104
|
+
result = '/'.join(normalized_parts)
|
|
105
|
+
|
|
106
|
+
# Use os.path.normpath for additional cleanup (handles . and ..)
|
|
107
|
+
# but it converts to OS-specific separators, so convert back
|
|
108
|
+
result = os.path.normpath(result)
|
|
109
|
+
|
|
110
|
+
# On Windows, normpath uses backslashes; convert back to forward slashes
|
|
111
|
+
result = result.replace('\\', '/')
|
|
112
|
+
|
|
113
|
+
return result
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def expand_template(template: str, context: Dict[str, Any]) -> str:
|
|
117
|
+
"""
|
|
118
|
+
Expand a path template with placeholder values.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
template: Path template with {placeholder} syntax
|
|
122
|
+
context: Dictionary of values to substitute
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Expanded path with normalized slashes
|
|
126
|
+
|
|
127
|
+
Example:
|
|
128
|
+
>>> expand_template(
|
|
129
|
+
... "frontend/src/components/{category}/{name}/{name}.tsx",
|
|
130
|
+
... {"name": "AssetCard", "category": "marketplace"}
|
|
131
|
+
... )
|
|
132
|
+
'frontend/src/components/marketplace/AssetCard/AssetCard.tsx'
|
|
133
|
+
"""
|
|
134
|
+
# Get base values from context (with empty string defaults)
|
|
135
|
+
name = context.get('name', '')
|
|
136
|
+
category = context.get('category', '')
|
|
137
|
+
dir_prefix = context.get('dir_prefix', '')
|
|
138
|
+
ext = context.get('ext', '')
|
|
139
|
+
language = context.get('language', '')
|
|
140
|
+
|
|
141
|
+
# Build the full set of available placeholders
|
|
142
|
+
placeholders = {
|
|
143
|
+
'name': name,
|
|
144
|
+
'category': category,
|
|
145
|
+
'dir_prefix': dir_prefix,
|
|
146
|
+
'ext': ext,
|
|
147
|
+
'language': language,
|
|
148
|
+
'name_snake': _to_snake_case(name),
|
|
149
|
+
'name_pascal': _to_pascal_case(name),
|
|
150
|
+
'name_kebab': _to_kebab_case(name),
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
# Perform substitution
|
|
154
|
+
result = template
|
|
155
|
+
for key, value in placeholders.items():
|
|
156
|
+
result = result.replace(f'{{{key}}}', str(value))
|
|
157
|
+
|
|
158
|
+
# Normalize the path to handle empty segments (double slashes)
|
|
159
|
+
result = _normalize_path(result)
|
|
160
|
+
|
|
161
|
+
return result
|
pdd/template_registry.py
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import shutil
|
|
5
|
+
from collections.abc import Iterable as IterableABC
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Dict, Iterable, List, Optional, Tuple
|
|
9
|
+
|
|
10
|
+
from importlib.resources import as_file
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from importlib.resources import files as pkg_files
|
|
14
|
+
except ImportError: # pragma: no cover
|
|
15
|
+
# Fallback for Python < 3.11 if needed.
|
|
16
|
+
from importlib_resources import files as pkg_files # type: ignore[attr-defined]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
_FRONT_MATTER_PATTERN = re.compile(r"^---\s*\r?\n(.*?)\r?\n---\s*\r?\n?", re.DOTALL)
|
|
20
|
+
_SOURCE_PRIORITY = {"packaged": 0, "project": 1}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class TemplateMeta:
|
|
25
|
+
name: str
|
|
26
|
+
path: Path
|
|
27
|
+
description: str = ""
|
|
28
|
+
version: str = ""
|
|
29
|
+
tags: List[str] = field(default_factory=list)
|
|
30
|
+
language: str = ""
|
|
31
|
+
output: str = ""
|
|
32
|
+
variables: Dict[str, Any] = field(default_factory=dict)
|
|
33
|
+
usage: Dict[str, Any] = field(default_factory=dict)
|
|
34
|
+
discover: Dict[str, Any] = field(default_factory=dict)
|
|
35
|
+
output_schema: Dict[str, Any] = field(default_factory=dict)
|
|
36
|
+
notes: str = ""
|
|
37
|
+
source: str = "packaged"
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def alias(self) -> str:
|
|
41
|
+
return self.path.stem
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _safe_load_yaml(text: str) -> Optional[Dict[str, Any]]:
|
|
45
|
+
try:
|
|
46
|
+
import yaml # type: ignore
|
|
47
|
+
except ImportError as exc: # pragma: no cover - PyYAML is an install requirement
|
|
48
|
+
raise RuntimeError("PyYAML is required to parse template front matter") from exc
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
data = yaml.safe_load(text) or {}
|
|
52
|
+
except Exception:
|
|
53
|
+
return None
|
|
54
|
+
if not isinstance(data, dict):
|
|
55
|
+
return None
|
|
56
|
+
return data
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _parse_front_matter(text: str) -> Tuple[Optional[Dict[str, Any]], str]:
|
|
60
|
+
match = _FRONT_MATTER_PATTERN.match(text)
|
|
61
|
+
if not match:
|
|
62
|
+
return None, text
|
|
63
|
+
meta_text = match.group(1)
|
|
64
|
+
rest = text[match.end():]
|
|
65
|
+
meta = _safe_load_yaml(meta_text)
|
|
66
|
+
return meta, rest
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _normalize_tags(tags: Any) -> List[str]:
|
|
70
|
+
if tags is None:
|
|
71
|
+
return []
|
|
72
|
+
if isinstance(tags, str):
|
|
73
|
+
return [tags.lower()]
|
|
74
|
+
if isinstance(tags, IterableABC):
|
|
75
|
+
normalized: List[str] = []
|
|
76
|
+
for tag in tags:
|
|
77
|
+
if tag is None:
|
|
78
|
+
continue
|
|
79
|
+
normalized.append(str(tag).lower())
|
|
80
|
+
return normalized
|
|
81
|
+
return []
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _ensure_mapping(value: Any) -> Dict[str, Any]:
|
|
85
|
+
if isinstance(value, dict):
|
|
86
|
+
return dict(value)
|
|
87
|
+
return {}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _normalize_meta(raw: Dict[str, Any], path: Path, source: str) -> TemplateMeta:
|
|
91
|
+
name = str(raw.get("name") or path.stem)
|
|
92
|
+
description = str(raw.get("description") or "")
|
|
93
|
+
version = str(raw.get("version") or "")
|
|
94
|
+
language = str(raw.get("language") or "")
|
|
95
|
+
output = str(raw.get("output") or "")
|
|
96
|
+
tags = _normalize_tags(raw.get("tags"))
|
|
97
|
+
notes_raw = raw.get("notes")
|
|
98
|
+
notes = "" if notes_raw is None else str(notes_raw)
|
|
99
|
+
|
|
100
|
+
return TemplateMeta(
|
|
101
|
+
name=name,
|
|
102
|
+
path=path.resolve(),
|
|
103
|
+
description=description,
|
|
104
|
+
version=version,
|
|
105
|
+
tags=tags,
|
|
106
|
+
language=language,
|
|
107
|
+
output=output,
|
|
108
|
+
variables=_ensure_mapping(raw.get("variables")),
|
|
109
|
+
usage=_ensure_mapping(raw.get("usage")),
|
|
110
|
+
discover=_ensure_mapping(raw.get("discover")),
|
|
111
|
+
output_schema=_ensure_mapping(raw.get("output_schema")),
|
|
112
|
+
notes=notes,
|
|
113
|
+
source=source,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _iter_project_templates() -> Iterable[Path]:
|
|
118
|
+
root = Path.cwd() / "prompts"
|
|
119
|
+
if not root.exists():
|
|
120
|
+
return ()
|
|
121
|
+
return (path for path in root.rglob("*.prompt") if path.is_file())
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _iter_packaged_templates() -> Iterable[Path]:
|
|
125
|
+
try:
|
|
126
|
+
pkg_root = pkg_files("pdd").joinpath("templates")
|
|
127
|
+
except ModuleNotFoundError: # pragma: no cover - package missing
|
|
128
|
+
return ()
|
|
129
|
+
if not pkg_root.is_dir():
|
|
130
|
+
return ()
|
|
131
|
+
|
|
132
|
+
resolved: List[Path] = []
|
|
133
|
+
for entry in pkg_root.rglob("*.prompt"): # type: ignore[attr-defined]
|
|
134
|
+
try:
|
|
135
|
+
with as_file(entry) as concrete:
|
|
136
|
+
path = Path(concrete)
|
|
137
|
+
if path.is_file():
|
|
138
|
+
resolved.append(path)
|
|
139
|
+
except FileNotFoundError:
|
|
140
|
+
continue
|
|
141
|
+
return resolved
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _load_meta_from_path(path: Path, source: str) -> Optional[TemplateMeta]:
|
|
145
|
+
try:
|
|
146
|
+
text = path.read_text(encoding="utf-8")
|
|
147
|
+
except OSError:
|
|
148
|
+
return None
|
|
149
|
+
front_matter, _ = _parse_front_matter(text)
|
|
150
|
+
if not front_matter:
|
|
151
|
+
return None
|
|
152
|
+
return _normalize_meta(front_matter, path, source)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _index_templates() -> Tuple[Dict[str, TemplateMeta], Dict[str, TemplateMeta]]:
|
|
156
|
+
by_name: Dict[str, TemplateMeta] = {}
|
|
157
|
+
priority: Dict[str, int] = {}
|
|
158
|
+
|
|
159
|
+
def register(meta: TemplateMeta) -> None:
|
|
160
|
+
current_priority = priority.get(meta.name, -1)
|
|
161
|
+
new_priority = _SOURCE_PRIORITY.get(meta.source, 0)
|
|
162
|
+
if new_priority < current_priority:
|
|
163
|
+
return
|
|
164
|
+
by_name[meta.name] = meta
|
|
165
|
+
priority[meta.name] = new_priority
|
|
166
|
+
|
|
167
|
+
for path in _iter_packaged_templates():
|
|
168
|
+
meta = _load_meta_from_path(Path(path), "packaged")
|
|
169
|
+
if meta:
|
|
170
|
+
register(meta)
|
|
171
|
+
|
|
172
|
+
for path in _iter_project_templates():
|
|
173
|
+
meta = _load_meta_from_path(Path(path), "project")
|
|
174
|
+
if meta:
|
|
175
|
+
register(meta)
|
|
176
|
+
|
|
177
|
+
lookup = dict(by_name)
|
|
178
|
+
lookup_priority = priority.copy()
|
|
179
|
+
|
|
180
|
+
for meta in by_name.values():
|
|
181
|
+
alias = meta.alias
|
|
182
|
+
alias_priority = lookup_priority.get(alias, -1)
|
|
183
|
+
meta_priority = priority.get(meta.name, 0)
|
|
184
|
+
if alias_priority <= meta_priority:
|
|
185
|
+
lookup[alias] = meta
|
|
186
|
+
lookup_priority[alias] = meta_priority
|
|
187
|
+
|
|
188
|
+
return by_name, lookup
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _meta_to_payload(meta: TemplateMeta) -> Dict[str, Any]:
|
|
192
|
+
return {
|
|
193
|
+
"name": meta.name,
|
|
194
|
+
"path": str(meta.path),
|
|
195
|
+
"description": meta.description,
|
|
196
|
+
"version": meta.version,
|
|
197
|
+
"tags": list(meta.tags),
|
|
198
|
+
"language": meta.language,
|
|
199
|
+
"output": meta.output,
|
|
200
|
+
"variables": dict(meta.variables),
|
|
201
|
+
"usage": dict(meta.usage),
|
|
202
|
+
"discover": dict(meta.discover),
|
|
203
|
+
"output_schema": dict(meta.output_schema),
|
|
204
|
+
"notes": meta.notes,
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def list_templates(filter_tag: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
209
|
+
by_name, _ = _index_templates()
|
|
210
|
+
normalized_tag = filter_tag.lower() if filter_tag else None
|
|
211
|
+
items: List[Dict[str, Any]] = []
|
|
212
|
+
for meta in by_name.values():
|
|
213
|
+
if normalized_tag and normalized_tag not in meta.tags:
|
|
214
|
+
continue
|
|
215
|
+
items.append({
|
|
216
|
+
"name": meta.name,
|
|
217
|
+
"path": str(meta.path),
|
|
218
|
+
"description": meta.description,
|
|
219
|
+
"version": meta.version,
|
|
220
|
+
"tags": list(meta.tags),
|
|
221
|
+
})
|
|
222
|
+
items.sort(key=lambda item: item["name"].lower())
|
|
223
|
+
return items
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def load_template(name: str) -> Dict[str, Any]:
|
|
227
|
+
_, lookup = _index_templates()
|
|
228
|
+
meta = lookup.get(name)
|
|
229
|
+
if not meta:
|
|
230
|
+
raise FileNotFoundError(f"Template '{name}' not found.")
|
|
231
|
+
return _meta_to_payload(meta)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def show_template(name: str) -> Dict[str, Any]:
|
|
235
|
+
meta = load_template(name)
|
|
236
|
+
summary = {
|
|
237
|
+
"name": meta["name"],
|
|
238
|
+
"path": meta["path"],
|
|
239
|
+
"description": meta.get("description", ""),
|
|
240
|
+
"version": meta.get("version", ""),
|
|
241
|
+
"tags": meta.get("tags", []),
|
|
242
|
+
"language": meta.get("language", ""),
|
|
243
|
+
"output": meta.get("output", ""),
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
"summary": summary,
|
|
247
|
+
"variables": meta.get("variables", {}),
|
|
248
|
+
"usage": meta.get("usage", {}),
|
|
249
|
+
"discover": meta.get("discover", {}),
|
|
250
|
+
"output_schema": meta.get("output_schema", {}),
|
|
251
|
+
"notes": meta.get("notes", ""),
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def copy_template(name: str, dest_dir: str) -> str:
|
|
256
|
+
meta = load_template(name)
|
|
257
|
+
src = Path(meta["path"])
|
|
258
|
+
if not src.exists():
|
|
259
|
+
raise FileNotFoundError(f"Template '{name}' file is missing at {src}")
|
|
260
|
+
dest_root = Path(dest_dir)
|
|
261
|
+
dest_root.mkdir(parents=True, exist_ok=True)
|
|
262
|
+
dest_path = dest_root / src.name
|
|
263
|
+
shutil.copy2(src, dest_path)
|
|
264
|
+
return str(dest_path.resolve())
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: architecture/architecture_json
|
|
3
|
+
description: Unified architecture template for multiple tech stacks
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
tags: [architecture, template, json]
|
|
6
|
+
language: json
|
|
7
|
+
output: architecture.json
|
|
8
|
+
post_process_python: ./pdd/render_mermaid.py
|
|
9
|
+
post_process_args:
|
|
10
|
+
- "{INPUT_FILE}"
|
|
11
|
+
- "{APP_NAME}"
|
|
12
|
+
- "{OUTPUT_HTML}"
|
|
13
|
+
variables:
|
|
14
|
+
APP_NAME:
|
|
15
|
+
required: false
|
|
16
|
+
type: string
|
|
17
|
+
description: Optional app name for context.
|
|
18
|
+
example: Shop
|
|
19
|
+
PRD_FILE:
|
|
20
|
+
required: true
|
|
21
|
+
type: path
|
|
22
|
+
description: Primary product requirements document (PRD) describing scope and goals.
|
|
23
|
+
example_paths: [PRD.md, docs/specs.md, docs/product/prd.md]
|
|
24
|
+
example_content: |
|
|
25
|
+
Title: Order Management MVP
|
|
26
|
+
Goals: Enable customers to create and track orders end-to-end.
|
|
27
|
+
Key Features:
|
|
28
|
+
- Create Order: id, user_id, items[], total, status
|
|
29
|
+
- View Order: details page with status timeline
|
|
30
|
+
- List Orders: filter by status, date, user
|
|
31
|
+
Non-Functional Requirements:
|
|
32
|
+
- P95 latency < 300ms for read endpoints
|
|
33
|
+
- Error rate < 0.1%
|
|
34
|
+
TECH_STACK_FILE:
|
|
35
|
+
required: false
|
|
36
|
+
type: path
|
|
37
|
+
description: Tech stack overview (languages, frameworks, infrastructure, and tools).
|
|
38
|
+
example_paths: [docs/tech_stack.md, docs/architecture/stack.md]
|
|
39
|
+
example_content: |
|
|
40
|
+
Backend: Python (FastAPI), Postgres (SQLAlchemy), PyTest
|
|
41
|
+
Frontend: Next.js (TypeScript), shadcn/ui, Tailwind CSS
|
|
42
|
+
API: REST
|
|
43
|
+
Auth: Firebase Auth (GitHub Device Flow), JWT for API
|
|
44
|
+
Infra: Vercel (frontend), Cloud Run (backend), Cloud SQL (Postgres)
|
|
45
|
+
Observability: OpenTelemetry traces, Cloud Logging
|
|
46
|
+
DOC_FILES:
|
|
47
|
+
required: false
|
|
48
|
+
type: list
|
|
49
|
+
description: Additional documentation files (comma/newline-separated).
|
|
50
|
+
example_paths: [docs/ux.md, docs/components.md]
|
|
51
|
+
example_content: |
|
|
52
|
+
Design overview, patterns and constraints
|
|
53
|
+
INCLUDE_FILES:
|
|
54
|
+
required: false
|
|
55
|
+
type: list
|
|
56
|
+
description: Specific source files to include (comma/newline-separated).
|
|
57
|
+
example_paths: [src/app.py, src/api.py, frontend/app/layout.tsx, frontend/app/page.tsx]
|
|
58
|
+
usage:
|
|
59
|
+
generate:
|
|
60
|
+
- name: Minimal (PRD only)
|
|
61
|
+
command: pdd generate --template architecture/architecture_json -e PRD_FILE=docs/specs.md --output architecture.json
|
|
62
|
+
- name: With tech stack overview
|
|
63
|
+
command: pdd generate --template architecture/architecture_json -e PRD_FILE=docs/specs.md -e TECH_STACK_FILE=docs/tech_stack.md --output architecture.json
|
|
64
|
+
|
|
65
|
+
discover:
|
|
66
|
+
enabled: false
|
|
67
|
+
max_per_pattern: 5
|
|
68
|
+
max_total: 10
|
|
69
|
+
|
|
70
|
+
output_schema:
|
|
71
|
+
type: array
|
|
72
|
+
items:
|
|
73
|
+
type: object
|
|
74
|
+
required: [reason, description, dependencies, priority, filename, filepath]
|
|
75
|
+
properties:
|
|
76
|
+
reason: { type: string }
|
|
77
|
+
description: { type: string }
|
|
78
|
+
dependencies: { type: array, items: { type: string } }
|
|
79
|
+
priority: { type: integer, minimum: 1 }
|
|
80
|
+
filename: { type: string }
|
|
81
|
+
filepath: { type: string }
|
|
82
|
+
tags: { type: array, items: { type: string } }
|
|
83
|
+
interface:
|
|
84
|
+
type: object
|
|
85
|
+
properties:
|
|
86
|
+
type: { type: string, enum: [component, page, module, api, graphql, cli, job, message, config] }
|
|
87
|
+
component: { type: object }
|
|
88
|
+
page:
|
|
89
|
+
type: object
|
|
90
|
+
properties:
|
|
91
|
+
route: { type: string }
|
|
92
|
+
params:
|
|
93
|
+
type: array
|
|
94
|
+
items:
|
|
95
|
+
type: object
|
|
96
|
+
required: [name, type]
|
|
97
|
+
properties:
|
|
98
|
+
name: { type: string }
|
|
99
|
+
type: { type: string }
|
|
100
|
+
description: { type: string }
|
|
101
|
+
dataSources:
|
|
102
|
+
type: array
|
|
103
|
+
items:
|
|
104
|
+
type: object
|
|
105
|
+
required: [kind, source]
|
|
106
|
+
properties:
|
|
107
|
+
kind: { type: string, enum: [api, query, stream, file, cache, message, job, other] }
|
|
108
|
+
source: { type: string }
|
|
109
|
+
method: { type: string }
|
|
110
|
+
description: { type: string }
|
|
111
|
+
auth: { type: string }
|
|
112
|
+
inputs: { type: array, items: { type: string } }
|
|
113
|
+
outputs: { type: array, items: { type: string } }
|
|
114
|
+
refreshInterval: { type: string }
|
|
115
|
+
notes: { type: string }
|
|
116
|
+
layout: { type: object }
|
|
117
|
+
module: { type: object }
|
|
118
|
+
api: { type: object }
|
|
119
|
+
graphql: { type: object }
|
|
120
|
+
cli: { type: object }
|
|
121
|
+
job: { type: object }
|
|
122
|
+
message: { type: object }
|
|
123
|
+
config: { type: object }
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
Purpose: Produce an architecture JSON that enumerates prompt files to generate code files for the project.
|
|
127
|
+
|
|
128
|
+
<PRD_FILE><include>${PRD_FILE}</include></PRD_FILE>
|
|
129
|
+
<TECH_STACK_FILE><include>${TECH_STACK_FILE}</include></TECH_STACK_FILE>
|
|
130
|
+
<DOC_FILES><include-many>${DOC_FILES}</include-many></DOC_FILES>
|
|
131
|
+
|
|
132
|
+
<INCLUDE_FILES><include-many>${INCLUDE_FILES}</include-many></INCLUDE_FILES>
|
|
133
|
+
|
|
134
|
+
INSTRUCTIONS:
|
|
135
|
+
- Use only the facts from the included documents and files. Do not invent technologies or filenames.
|
|
136
|
+
- If TECH_STACK_FILE is absent, infer a reasonable tech stack from the PRD and included files; state key assumptions within each item's description.
|
|
137
|
+
- Output a single top-level JSON array of items. Each item must include:
|
|
138
|
+
- reason (briefly explain why this code module needs to exist), description, dependencies (filenames), priority (1 = highest), filename, filepath, optional tags.
|
|
139
|
+
- interface: include only the applicable sub-object (component, page, module, api, graphql, cli, job, message, or config). Omit all non-applicable sub-objects entirely.
|
|
140
|
+
- When interface.type is "page", each entry in `dataSources` must be an object with at least `kind` and `source` (e.g., URL or identifier). The `kind` field MUST be exactly one of: `"api"`, `"query"`, `"stream"`, `"file"`, `"cache"`, `"message"`, `"job"`, or `"other"`. Do not invent new values like `"api/mutation"`; instead, use `"api"` (for any HTTP/REST/GraphQL endpoint) or `"other"` and describe details such as queries vs. mutations in `description` or `notes`. Provide `method`, `description`, and any other useful metadata when known.
|
|
141
|
+
- Valid JSON only. No comments or trailing commas.
|
|
142
|
+
|
|
143
|
+
OUTPUT FORMAT - CRITICAL: Return a raw JSON array, NOT an object with "items" or "data" wrapper:
|
|
144
|
+
```json
|
|
145
|
+
[
|
|
146
|
+
{
|
|
147
|
+
"reason": "Core data models needed by all other modules",
|
|
148
|
+
"description": "Defines Order, User, and Item data models with validation",
|
|
149
|
+
"dependencies": [],
|
|
150
|
+
"priority": 1,
|
|
151
|
+
"filename": "models_Python.prompt",
|
|
152
|
+
"filepath": "src/models.py",
|
|
153
|
+
"tags": ["backend", "data"],
|
|
154
|
+
"interface": {
|
|
155
|
+
"type": "module",
|
|
156
|
+
"module": {
|
|
157
|
+
"functions": [
|
|
158
|
+
{"name": "Order", "signature": "class Order(BaseModel)", "returns": "Order instance"}
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"reason": "API endpoints for order management",
|
|
165
|
+
"description": "REST API for creating, reading, updating orders",
|
|
166
|
+
"dependencies": ["models_Python.prompt"],
|
|
167
|
+
"priority": 2,
|
|
168
|
+
"filename": "orders_api_Python.prompt",
|
|
169
|
+
"filepath": "src/api/orders.py",
|
|
170
|
+
"tags": ["backend", "api"],
|
|
171
|
+
"interface": {
|
|
172
|
+
"type": "api",
|
|
173
|
+
"api": {
|
|
174
|
+
"endpoints": [
|
|
175
|
+
{"method": "POST", "path": "/orders", "auth": "jwt"},
|
|
176
|
+
{"method": "GET", "path": "/orders/{id}", "auth": "jwt"}
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
]
|
|
182
|
+
```
|
|
183
|
+
WRONG (do NOT do this):
|
|
184
|
+
```json
|
|
185
|
+
{"items": [...]} // WRONG - no wrapper objects!
|
|
186
|
+
{"data": [...]} // WRONG - no wrapper objects!
|
|
187
|
+
{"type": "array", "items": [...]} // WRONG - this is schema, not output!
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
INTERFACE TYPES (emit only applicable):
|
|
191
|
+
- page: route (string), params? (array of {name, type, description?}), dataSources? (array), layout? (object)
|
|
192
|
+
- component: props (array of {name, type, required?}), emits? (array), context? (array)
|
|
193
|
+
- module: functions (array of {name, signature, returns?, errors?, sideEffects?})
|
|
194
|
+
- api: endpoints (array of {method, path, auth?, requestSchema?, responseSchema?, errors?})
|
|
195
|
+
- graphql: sdl? (string) or operations {queries?[], mutations?[], subscriptions?[]}
|
|
196
|
+
- cli: commands (array of {name, args?[], flags?[], exitCodes?[]}), io? {stdin?, stdout?}
|
|
197
|
+
- job: trigger {schedule? | event?}, inputs? (array), outputs? (array), retryPolicy? (string)
|
|
198
|
+
- message: topics (array of {name, direction: "publish"|"subscribe", schema?, qos?})
|
|
199
|
+
- config: keys (array of {name, type, default?, required?, source: "env"|"file"|"secret"})
|
|
200
|
+
|
|
201
|
+
FILENAME CONVENTIONS:
|
|
202
|
+
- The "filename" field is the prompt filename to generate (not the code file). Use PDD convention: <base>_<LangOrFramework>.prompt where <LangOrFramework> matches the tech stack.
|
|
203
|
+
- Examples (adapt to your stack):
|
|
204
|
+
- Next.js (TypeScript React): page_TypeScriptReact.prompt -> generates page.tsx; layout_TypeScriptReact.prompt -> layout.tsx
|
|
205
|
+
- Python backend: api_Python.prompt -> api.py; orders_Python.prompt -> orders.py
|
|
206
|
+
- Choose descriptive <base> names (e.g., orders_page, orders_api) and keep names consistent across dependencies.
|
|
207
|
+
|
|
208
|
+
FILEPATH CONVENTIONS:
|
|
209
|
+
- The "filepath" field specifies the path of the output source file from the source tree root, using conventions appropriate for the language and framework.
|
|
210
|
+
- Examples (adapt to your stack):
|
|
211
|
+
- Next.js app router: app/orders/page.tsx, app/layout.tsx, app/api/orders/route.ts
|
|
212
|
+
- Next.js pages router: pages/orders.tsx, pages/api/orders.ts
|
|
213
|
+
- Python FastAPI: src/api.py, src/orders.py, src/models/order.py
|
|
214
|
+
- React components: src/components/OrderList.tsx, src/hooks/useOrders.ts
|
|
215
|
+
- Config files: .env.example, pyproject.toml, package.json
|
|
216
|
+
- Use forward slashes (/) for path separators regardless of OS.
|
|
217
|
+
- Include the appropriate file extension for the target language (.tsx, .py, .rs, .go, etc.).
|
|
218
|
+
- Follow standard directory structures for the framework (e.g., app/ for Next.js 13+, src/ for typical React/Python projects).
|
|
219
|
+
|
|
220
|
+
DEPENDENCY RULES:
|
|
221
|
+
- The "dependencies" array must list other items by their prompt filenames (the "filename" values), not code filenames.
|
|
222
|
+
- Do not reference files that are not part of this array unless they were explicitly provided via INCLUDE_FILES/DOC_FILES.
|
|
223
|
+
- Avoid cycles; if a cycle is necessary, justify it in the description and clarify initialization order.
|
|
224
|
+
|
|
225
|
+
PRIORITY AND ORDERING:
|
|
226
|
+
- Use unique integer priorities starting at 1 without gaps (1,2,3,...).
|
|
227
|
+
- Sort the top-level array by ascending priority.
|
|
228
|
+
|
|
229
|
+
TAGS (optional):
|
|
230
|
+
- Use short, lower-case tags for slicing (e.g., ["frontend","nextjs"], ["backend","api"], ["config"]).
|
|
231
|
+
|
|
232
|
+
CONTENT GUIDANCE:
|
|
233
|
+
- Descriptions must be architectural and actionable: responsibilities, interfaces, error handling, cross-cutting concerns.
|
|
234
|
+
- For API items, outline endpoints (method, path, auth) and high-level request/response shapes.
|
|
235
|
+
- For page/component items, include the route, key props, and data sources.
|
|
236
|
+
|
|
237
|
+
DO NOT INCLUDE the schema or these conventions in the output; return only the JSON array.
|