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.
Files changed (195) hide show
  1. pdd/__init__.py +40 -8
  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 +598 -0
  7. pdd/agentic_crash.py +534 -0
  8. pdd/agentic_e2e_fix.py +319 -0
  9. pdd/agentic_e2e_fix_orchestrator.py +426 -0
  10. pdd/agentic_fix.py +1294 -0
  11. pdd/agentic_langtest.py +162 -0
  12. pdd/agentic_update.py +387 -0
  13. pdd/agentic_verify.py +183 -0
  14. pdd/architecture_sync.py +565 -0
  15. pdd/auth_service.py +210 -0
  16. pdd/auto_deps_main.py +71 -51
  17. pdd/auto_include.py +245 -5
  18. pdd/auto_update.py +125 -47
  19. pdd/bug_main.py +196 -23
  20. pdd/bug_to_unit_test.py +2 -0
  21. pdd/change_main.py +11 -4
  22. pdd/cli.py +22 -1181
  23. pdd/cmd_test_main.py +350 -150
  24. pdd/code_generator.py +60 -18
  25. pdd/code_generator_main.py +790 -57
  26. pdd/commands/__init__.py +48 -0
  27. pdd/commands/analysis.py +306 -0
  28. pdd/commands/auth.py +309 -0
  29. pdd/commands/connect.py +290 -0
  30. pdd/commands/fix.py +163 -0
  31. pdd/commands/generate.py +257 -0
  32. pdd/commands/maintenance.py +175 -0
  33. pdd/commands/misc.py +87 -0
  34. pdd/commands/modify.py +256 -0
  35. pdd/commands/report.py +144 -0
  36. pdd/commands/sessions.py +284 -0
  37. pdd/commands/templates.py +215 -0
  38. pdd/commands/utility.py +110 -0
  39. pdd/config_resolution.py +58 -0
  40. pdd/conflicts_main.py +8 -3
  41. pdd/construct_paths.py +589 -111
  42. pdd/context_generator.py +10 -2
  43. pdd/context_generator_main.py +175 -76
  44. pdd/continue_generation.py +53 -10
  45. pdd/core/__init__.py +33 -0
  46. pdd/core/cli.py +527 -0
  47. pdd/core/cloud.py +237 -0
  48. pdd/core/dump.py +554 -0
  49. pdd/core/errors.py +67 -0
  50. pdd/core/remote_session.py +61 -0
  51. pdd/core/utils.py +90 -0
  52. pdd/crash_main.py +262 -33
  53. pdd/data/language_format.csv +71 -63
  54. pdd/data/llm_model.csv +20 -18
  55. pdd/detect_change_main.py +5 -4
  56. pdd/docs/prompting_guide.md +864 -0
  57. pdd/docs/whitepaper_with_benchmarks/data_and_functions/benchmark_analysis.py +495 -0
  58. pdd/docs/whitepaper_with_benchmarks/data_and_functions/creation_compare.py +528 -0
  59. pdd/fix_code_loop.py +523 -95
  60. pdd/fix_code_module_errors.py +6 -2
  61. pdd/fix_error_loop.py +491 -92
  62. pdd/fix_errors_from_unit_tests.py +4 -3
  63. pdd/fix_main.py +278 -21
  64. pdd/fix_verification_errors.py +12 -100
  65. pdd/fix_verification_errors_loop.py +529 -286
  66. pdd/fix_verification_main.py +294 -89
  67. pdd/frontend/dist/assets/index-B5DZHykP.css +1 -0
  68. pdd/frontend/dist/assets/index-DQ3wkeQ2.js +449 -0
  69. pdd/frontend/dist/index.html +376 -0
  70. pdd/frontend/dist/logo.svg +33 -0
  71. pdd/generate_output_paths.py +139 -15
  72. pdd/generate_test.py +218 -146
  73. pdd/get_comment.py +19 -44
  74. pdd/get_extension.py +8 -9
  75. pdd/get_jwt_token.py +318 -22
  76. pdd/get_language.py +8 -7
  77. pdd/get_run_command.py +75 -0
  78. pdd/get_test_command.py +68 -0
  79. pdd/git_update.py +70 -19
  80. pdd/incremental_code_generator.py +2 -2
  81. pdd/insert_includes.py +13 -4
  82. pdd/llm_invoke.py +1711 -181
  83. pdd/load_prompt_template.py +19 -12
  84. pdd/path_resolution.py +140 -0
  85. pdd/pdd_completion.fish +25 -2
  86. pdd/pdd_completion.sh +30 -4
  87. pdd/pdd_completion.zsh +79 -4
  88. pdd/postprocess.py +14 -4
  89. pdd/preprocess.py +293 -24
  90. pdd/preprocess_main.py +41 -6
  91. pdd/prompts/agentic_bug_step10_pr_LLM.prompt +182 -0
  92. pdd/prompts/agentic_bug_step1_duplicate_LLM.prompt +73 -0
  93. pdd/prompts/agentic_bug_step2_docs_LLM.prompt +129 -0
  94. pdd/prompts/agentic_bug_step3_triage_LLM.prompt +95 -0
  95. pdd/prompts/agentic_bug_step4_reproduce_LLM.prompt +97 -0
  96. pdd/prompts/agentic_bug_step5_root_cause_LLM.prompt +123 -0
  97. pdd/prompts/agentic_bug_step6_test_plan_LLM.prompt +107 -0
  98. pdd/prompts/agentic_bug_step7_generate_LLM.prompt +172 -0
  99. pdd/prompts/agentic_bug_step8_verify_LLM.prompt +119 -0
  100. pdd/prompts/agentic_bug_step9_e2e_test_LLM.prompt +289 -0
  101. pdd/prompts/agentic_change_step10_identify_issues_LLM.prompt +1006 -0
  102. pdd/prompts/agentic_change_step11_fix_issues_LLM.prompt +984 -0
  103. pdd/prompts/agentic_change_step12_create_pr_LLM.prompt +131 -0
  104. pdd/prompts/agentic_change_step1_duplicate_LLM.prompt +73 -0
  105. pdd/prompts/agentic_change_step2_docs_LLM.prompt +101 -0
  106. pdd/prompts/agentic_change_step3_research_LLM.prompt +126 -0
  107. pdd/prompts/agentic_change_step4_clarify_LLM.prompt +164 -0
  108. pdd/prompts/agentic_change_step5_docs_change_LLM.prompt +981 -0
  109. pdd/prompts/agentic_change_step6_devunits_LLM.prompt +1005 -0
  110. pdd/prompts/agentic_change_step7_architecture_LLM.prompt +1044 -0
  111. pdd/prompts/agentic_change_step8_analyze_LLM.prompt +1027 -0
  112. pdd/prompts/agentic_change_step9_implement_LLM.prompt +1077 -0
  113. pdd/prompts/agentic_crash_explore_LLM.prompt +49 -0
  114. pdd/prompts/agentic_e2e_fix_step1_unit_tests_LLM.prompt +90 -0
  115. pdd/prompts/agentic_e2e_fix_step2_e2e_tests_LLM.prompt +91 -0
  116. pdd/prompts/agentic_e2e_fix_step3_root_cause_LLM.prompt +89 -0
  117. pdd/prompts/agentic_e2e_fix_step4_fix_e2e_tests_LLM.prompt +96 -0
  118. pdd/prompts/agentic_e2e_fix_step5_identify_devunits_LLM.prompt +91 -0
  119. pdd/prompts/agentic_e2e_fix_step6_create_unit_tests_LLM.prompt +106 -0
  120. pdd/prompts/agentic_e2e_fix_step7_verify_tests_LLM.prompt +116 -0
  121. pdd/prompts/agentic_e2e_fix_step8_run_pdd_fix_LLM.prompt +120 -0
  122. pdd/prompts/agentic_e2e_fix_step9_verify_all_LLM.prompt +146 -0
  123. pdd/prompts/agentic_fix_explore_LLM.prompt +45 -0
  124. pdd/prompts/agentic_fix_harvest_only_LLM.prompt +48 -0
  125. pdd/prompts/agentic_fix_primary_LLM.prompt +85 -0
  126. pdd/prompts/agentic_update_LLM.prompt +925 -0
  127. pdd/prompts/agentic_verify_explore_LLM.prompt +45 -0
  128. pdd/prompts/auto_include_LLM.prompt +122 -905
  129. pdd/prompts/change_LLM.prompt +3093 -1
  130. pdd/prompts/detect_change_LLM.prompt +686 -27
  131. pdd/prompts/example_generator_LLM.prompt +22 -1
  132. pdd/prompts/extract_code_LLM.prompt +5 -1
  133. pdd/prompts/extract_program_code_fix_LLM.prompt +7 -1
  134. pdd/prompts/extract_prompt_update_LLM.prompt +7 -8
  135. pdd/prompts/extract_promptline_LLM.prompt +17 -11
  136. pdd/prompts/find_verification_errors_LLM.prompt +6 -0
  137. pdd/prompts/fix_code_module_errors_LLM.prompt +12 -2
  138. pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +9 -0
  139. pdd/prompts/fix_verification_errors_LLM.prompt +22 -0
  140. pdd/prompts/generate_test_LLM.prompt +41 -7
  141. pdd/prompts/generate_test_from_example_LLM.prompt +115 -0
  142. pdd/prompts/increase_tests_LLM.prompt +1 -5
  143. pdd/prompts/insert_includes_LLM.prompt +316 -186
  144. pdd/prompts/prompt_code_diff_LLM.prompt +119 -0
  145. pdd/prompts/prompt_diff_LLM.prompt +82 -0
  146. pdd/prompts/trace_LLM.prompt +25 -22
  147. pdd/prompts/unfinished_prompt_LLM.prompt +85 -1
  148. pdd/prompts/update_prompt_LLM.prompt +22 -1
  149. pdd/pytest_output.py +127 -12
  150. pdd/remote_session.py +876 -0
  151. pdd/render_mermaid.py +236 -0
  152. pdd/server/__init__.py +52 -0
  153. pdd/server/app.py +335 -0
  154. pdd/server/click_executor.py +587 -0
  155. pdd/server/executor.py +338 -0
  156. pdd/server/jobs.py +661 -0
  157. pdd/server/models.py +241 -0
  158. pdd/server/routes/__init__.py +31 -0
  159. pdd/server/routes/architecture.py +451 -0
  160. pdd/server/routes/auth.py +364 -0
  161. pdd/server/routes/commands.py +929 -0
  162. pdd/server/routes/config.py +42 -0
  163. pdd/server/routes/files.py +603 -0
  164. pdd/server/routes/prompts.py +1322 -0
  165. pdd/server/routes/websocket.py +473 -0
  166. pdd/server/security.py +243 -0
  167. pdd/server/terminal_spawner.py +209 -0
  168. pdd/server/token_counter.py +222 -0
  169. pdd/setup_tool.py +648 -0
  170. pdd/simple_math.py +2 -0
  171. pdd/split_main.py +3 -2
  172. pdd/summarize_directory.py +237 -195
  173. pdd/sync_animation.py +8 -4
  174. pdd/sync_determine_operation.py +839 -112
  175. pdd/sync_main.py +351 -57
  176. pdd/sync_orchestration.py +1400 -756
  177. pdd/sync_tui.py +848 -0
  178. pdd/template_expander.py +161 -0
  179. pdd/template_registry.py +264 -0
  180. pdd/templates/architecture/architecture_json.prompt +237 -0
  181. pdd/templates/generic/generate_prompt.prompt +174 -0
  182. pdd/trace.py +168 -12
  183. pdd/trace_main.py +4 -3
  184. pdd/track_cost.py +140 -63
  185. pdd/unfinished_prompt.py +51 -4
  186. pdd/update_main.py +567 -67
  187. pdd/update_model_costs.py +2 -2
  188. pdd/update_prompt.py +19 -4
  189. {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/METADATA +29 -11
  190. pdd_cli-0.0.118.dist-info/RECORD +227 -0
  191. {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/licenses/LICENSE +1 -1
  192. pdd_cli-0.0.45.dist-info/RECORD +0 -116
  193. {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/WHEEL +0 -0
  194. {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/entry_points.txt +0 -0
  195. {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/top_level.txt +0 -0
@@ -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
@@ -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.