pdd-cli 0.0.61__py3-none-any.whl → 0.0.62__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.

Potentially problematic release.


This version of pdd-cli might be problematic. Click here for more details.

pdd/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """PDD - Prompt Driven Development"""
2
2
 
3
- __version__ = "0.0.61"
3
+ __version__ = "0.0.62"
4
4
 
5
5
  # Strength parameter used for LLM extraction across the codebase
6
6
  # Used in postprocessing, XML tagging, code generation, and other extraction
pdd/cli.py CHANGED
@@ -14,9 +14,11 @@ from typing import Any, Dict, List, Optional, Tuple
14
14
  from pathlib import Path # Import Path
15
15
 
16
16
  import click
17
+ from rich import box
17
18
  from rich.console import Console
18
- from rich.theme import Theme
19
19
  from rich.markup import MarkupError, escape
20
+ from rich.table import Table
21
+ from rich.theme import Theme
20
22
 
21
23
  # --- Relative Imports for Internal Modules ---
22
24
  from . import DEFAULT_STRENGTH, __version__, DEFAULT_TIME
@@ -449,20 +451,138 @@ def templates_show(name: str):
449
451
  try:
450
452
  data = template_registry.show_template(name)
451
453
  summary = data.get("summary", {})
452
- console.print(f"[bold]{summary.get('name','')}[/bold] — {summary.get('description','')}")
453
- console.print(f"Version: {summary.get('version','')} Tags: {', '.join(summary.get('tags',[]))}")
454
- console.print(f"Language: {summary.get('language','')} Output: {summary.get('output','')}")
455
- console.print(f"Path: {summary.get('path','')}")
454
+
455
+ def _render_key_value_table(title: Optional[str], items: List[Tuple[str, Any]], *, highlight_path: bool = False):
456
+ """Render a 2-column Rich table for key/value pairs."""
457
+
458
+ table = Table(show_header=False, box=box.SIMPLE, expand=True)
459
+ table.add_column("Field", style="info", no_wrap=True)
460
+ table.add_column("Value", overflow="fold")
461
+
462
+ added_rows = False
463
+ for label, value in items:
464
+ if value in (None, "", [], {}):
465
+ continue
466
+ if isinstance(value, (list, tuple)):
467
+ value_str = ", ".join(str(v) for v in value)
468
+ else:
469
+ value_str = str(value)
470
+
471
+ if highlight_path and label.lower() == "path":
472
+ value_markup = f"[path]{escape(value_str)}[/path]"
473
+ else:
474
+ value_markup = escape(value_str)
475
+
476
+ table.add_row(label, value_markup)
477
+ added_rows = True
478
+
479
+ if added_rows:
480
+ if title:
481
+ console.print(f"[info]{title}[/info]")
482
+ console.print(table)
483
+
484
+ summary_items = [
485
+ ("Name", summary.get("name")),
486
+ ("Description", summary.get("description")),
487
+ ("Version", summary.get("version")),
488
+ ("Tags", summary.get("tags", [])),
489
+ ("Language", summary.get("language")),
490
+ ("Output", summary.get("output")),
491
+ ("Path", summary.get("path")),
492
+ ]
493
+ _render_key_value_table("Template Summary:", summary_items, highlight_path=True)
494
+
456
495
  if data.get("variables"):
457
496
  console.print("\n[info]Variables:[/info]")
458
- for k, v in data["variables"].items():
459
- console.print(f"- {k}: {v}")
497
+ variables_table = Table(box=box.SIMPLE_HEAD, show_lines=False, expand=True)
498
+ variables_table.add_column("Name", style="bold", no_wrap=True)
499
+ variables_table.add_column("Required", style="info", no_wrap=True)
500
+ variables_table.add_column("Type", no_wrap=True)
501
+ variables_table.add_column("Description", overflow="fold")
502
+ variables_table.add_column("Default/Examples", overflow="fold")
503
+
504
+ for var_name, var_meta in data["variables"].items():
505
+ required = var_meta.get("required")
506
+ if required is True:
507
+ required_str = "Yes"
508
+ elif required is False:
509
+ required_str = "No"
510
+ else:
511
+ required_str = "-"
512
+
513
+ var_type = escape(str(var_meta.get("type", "-")))
514
+ description = escape(str(var_meta.get("description", "")))
515
+
516
+ default_parts: List[str] = []
517
+ default_value = var_meta.get("default")
518
+ if default_value not in (None, ""):
519
+ default_parts.append(f"default: {default_value}")
520
+
521
+ examples_value = var_meta.get("examples")
522
+ if examples_value:
523
+ if isinstance(examples_value, (list, tuple)):
524
+ examples_str = ", ".join(str(example) for example in examples_value)
525
+ else:
526
+ examples_str = str(examples_value)
527
+ default_parts.append(f"examples: {examples_str}")
528
+
529
+ example_paths_value = var_meta.get("example_paths")
530
+ if example_paths_value:
531
+ if isinstance(example_paths_value, (list, tuple)):
532
+ example_paths_str = ", ".join(str(example) for example in example_paths_value)
533
+ else:
534
+ example_paths_str = str(example_paths_value)
535
+ default_parts.append(f"paths: {example_paths_str}")
536
+
537
+ default_examples = "\n".join(default_parts) if default_parts else "-"
538
+
539
+ variables_table.add_row(
540
+ escape(str(var_name)),
541
+ required_str,
542
+ var_type,
543
+ description,
544
+ escape(default_examples),
545
+ )
546
+
547
+ console.print(variables_table)
548
+
460
549
  if data.get("usage"):
461
550
  console.print("\n[info]Usage:[/info]")
462
- console.print(data["usage"]) # raw; CLI may format later
551
+ usage = data["usage"]
552
+ if isinstance(usage, dict):
553
+ for group_name, entries in usage.items():
554
+ console.print(f"[bold]{escape(str(group_name))}[/bold]")
555
+ usage_table = Table(box=box.SIMPLE, show_lines=False, expand=True)
556
+ usage_table.add_column("Name", style="bold", no_wrap=True)
557
+ usage_table.add_column("Command", overflow="fold")
558
+
559
+ if isinstance(entries, (list, tuple)):
560
+ iterable_entries = entries
561
+ else:
562
+ iterable_entries = [entries]
563
+
564
+ for entry in iterable_entries:
565
+ if isinstance(entry, dict):
566
+ name_value = escape(str(entry.get("name", "")))
567
+ command_value = escape(str(entry.get("command", "")))
568
+ else:
569
+ name_value = "-"
570
+ command_value = escape(str(entry))
571
+ usage_table.add_row(name_value, f"[command]{command_value}[/command]")
572
+
573
+ if usage_table.row_count:
574
+ console.print(usage_table)
575
+ else:
576
+ console.print(usage)
577
+
463
578
  if data.get("discover"):
464
579
  console.print("\n[info]Discover:[/info]")
465
- console.print(data["discover"]) # raw dict
580
+ discover = data["discover"]
581
+ if isinstance(discover, dict):
582
+ discover_items = [(str(key), value) for key, value in discover.items()]
583
+ _render_key_value_table(None, discover_items)
584
+ else:
585
+ console.print(discover)
466
586
  if data.get("output_schema"):
467
587
  console.print("\n[info]Output Schema:[/info]")
468
588
  try:
@@ -64,3 +64,6 @@ Log,del,.log
64
64
  reStructuredText,del,.rst
65
65
  Text,del,.txt
66
66
  INI,;,.ini
67
+ Verilog,//,.v
68
+ Systemverilog,//,.sv
69
+ Prisma,///,.prisma
pdd/data/llm_model.csv CHANGED
@@ -2,7 +2,7 @@ provider,model,input,output,coding_arena_elo,base_url,api_key,max_reasoning_toke
2
2
  OpenAI,gpt-5-nano,0.05,0.4,1249,,OPENAI_API_KEY,0,True,none
3
3
  Google,vertex_ai/gemini-2.5-flash,0.15,0.6,1290,,VERTEX_CREDENTIALS,0,True,effort
4
4
  Google,gemini/gemini-2.5-pro,1.25,10.0,1360,,GEMINI_API_KEY,0,True,none
5
- Google,vertex_ai/claude-sonnet-4,3.0,15.0,1359,,VERTEX_CREDENTIALS,64000,True,budget
5
+ Google,vertex_ai/claude-sonnet-4-5,3.0,15.0,1359,,VERTEX_CREDENTIALS,64000,True,budget
6
6
  Google,vertex_ai/gemini-2.5-pro,1.25,10.0,1405,,VERTEX_CREDENTIALS,0,True,none
7
7
  OpenAI,gpt-5-mini,0.25,2.0,1325,,OPENAI_API_KEY,0,True,effort
8
8
  OpenAI,gpt-5,1.25,10.0,1482,,OPENAI_API_KEY,0,True,effort
@@ -15,6 +15,6 @@ OpenAI,openai/mlx-community/Qwen3-30B-A3B-4bit,0,0,1040,http://localhost:8080,,0
15
15
  OpenAI,lm_studio/openai-gpt-oss-120b-mlx-6,0.0001,0,1082,http://localhost:1234/v1,,0,True,none
16
16
  Fireworks,fireworks_ai/accounts/fireworks/models/glm-4p5,3.0,8.0,1364,,FIREWORKS_API_KEY,0,False,none
17
17
  OpenAI,groq/moonshotai/kimi-k2-instruct,1.0,3.0,1330,,GROQ_API_KEY,0,True,none
18
- Anthropic,anthropic/claude-sonnet-4-20250514,3.0,15.0,1356,,ANTHROPIC_API_KEY,64000,True,budget
18
+ Anthropic,anthropic/claude-sonnet-4-5-20250929,3.0,15.0,1356,,ANTHROPIC_API_KEY,64000,True,budget
19
19
  Anthropic,anthropic/claude-opus-4-1-20250805,3.0,15.0,1474,,ANTHROPIC_API_KEY,32000,True,budget
20
20
  Anthropic,anthropic/claude-3-5-haiku-20241022,3.0,15.0,1133,,ANTHROPIC_API_KEY,8192,True,budget
pdd/preprocess.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import os
2
2
  import re
3
3
  import subprocess
4
- from typing import List, Optional
4
+ from typing import List, Optional, Tuple
5
5
  import traceback
6
6
  from rich.console import Console
7
7
  from rich.panel import Panel
@@ -11,22 +11,107 @@ from rich.traceback import install
11
11
  install()
12
12
  console = Console()
13
13
 
14
+ # Debug/Instrumentation controls
15
+ _DEBUG_PREPROCESS = str(os.getenv("PDD_PREPROCESS_DEBUG", "")).lower() in ("1", "true", "yes", "on")
16
+ _DEBUG_OUTPUT_FILE = os.getenv("PDD_PREPROCESS_DEBUG_FILE") # Optional path to write a debug report
17
+ _DEBUG_EVENTS: List[str] = []
18
+
19
+ def _dbg(msg: str) -> None:
20
+ if _DEBUG_PREPROCESS:
21
+ console.print(f"[dim][PPD][preprocess][/dim] {escape(msg)}")
22
+ _DEBUG_EVENTS.append(msg)
23
+
24
+ def _write_debug_report() -> None:
25
+ if _DEBUG_PREPROCESS and _DEBUG_OUTPUT_FILE:
26
+ try:
27
+ with open(_DEBUG_OUTPUT_FILE, "w", encoding="utf-8") as fh:
28
+ fh.write("Preprocess Debug Report\n\n")
29
+ for line in _DEBUG_EVENTS:
30
+ fh.write(line + "\n")
31
+ except Exception:
32
+ # Avoid interfering with normal flow if writing fails
33
+ pass
34
+
35
+ def _extract_fence_spans(text: str) -> List[Tuple[int, int]]:
36
+ """Return list of (start, end) spans for fenced code blocks ```...```.
37
+
38
+ The spans are [start, end) indices in the original text.
39
+ """
40
+ spans: List[Tuple[int, int]] = []
41
+ try:
42
+ for m in re.finditer(r"```[\w\s]*\n[\s\S]*?```", text):
43
+ spans.append((m.start(), m.end()))
44
+ except Exception:
45
+ pass
46
+ return spans
47
+
48
+ def _is_inside_any_span(idx: int, spans: List[Tuple[int, int]]) -> bool:
49
+ for s, e in spans:
50
+ if s <= idx < e:
51
+ return True
52
+ return False
53
+
54
+ def _scan_risky_placeholders(text: str) -> Tuple[List[Tuple[int, str]], List[Tuple[int, str]]]:
55
+ """Scan for risky placeholders outside code fences.
56
+
57
+ Returns two lists of (line_no, snippet):
58
+ - single_brace: matches like {name} not doubled and not part of {{...}}
59
+ - template_brace: `${...}` occurrences (which include single { ... })
60
+ """
61
+ single_brace: List[Tuple[int, str]] = []
62
+ template_brace: List[Tuple[int, str]] = []
63
+ try:
64
+ fence_spans = _extract_fence_spans(text)
65
+ # Single-brace variable placeholders (avoid matching {{ or }})
66
+ for m in re.finditer(r"(?<!\{)\{([A-Za-z_][A-Za-z0-9_]*)\}(?!\})", text):
67
+ if not _is_inside_any_span(m.start(), fence_spans):
68
+ line_no = text.count("\n", 0, m.start()) + 1
69
+ single_brace.append((line_no, m.group(0)))
70
+ # JavaScript template placeholders like ${...}
71
+ for m in re.finditer(r"\$\{[^\}]+\}", text):
72
+ if not _is_inside_any_span(m.start(), fence_spans):
73
+ line_no = text.count("\n", 0, m.start()) + 1
74
+ template_brace.append((line_no, m.group(0)))
75
+ except Exception:
76
+ pass
77
+ return single_brace, template_brace
78
+
14
79
  def preprocess(prompt: str, recursive: bool = False, double_curly_brackets: bool = True, exclude_keys: Optional[List[str]] = None) -> str:
15
80
  try:
16
81
  if not prompt:
17
82
  console.print("[bold red]Error:[/bold red] Empty prompt provided")
18
83
  return ""
84
+ _DEBUG_EVENTS.clear()
85
+ _dbg(f"Start preprocess(recursive={recursive}, double_curly={double_curly_brackets}, exclude_keys={exclude_keys})")
86
+ _dbg(f"Initial length: {len(prompt)} characters")
19
87
  console.print(Panel("Starting prompt preprocessing", style="bold blue"))
20
88
  prompt = process_backtick_includes(prompt, recursive)
89
+ _dbg("After backtick includes processed")
21
90
  prompt = process_xml_tags(prompt, recursive)
91
+ _dbg("After XML-like tags processed")
22
92
  if double_curly_brackets:
23
93
  prompt = double_curly(prompt, exclude_keys)
94
+ _dbg("After double_curly execution")
95
+ # Scan for risky placeholders remaining outside code fences
96
+ singles, templates = _scan_risky_placeholders(prompt)
97
+ if singles:
98
+ _dbg(f"WARNING: Found {len(singles)} single-brace placeholders outside code fences (examples):")
99
+ for ln, frag in singles[:5]:
100
+ _dbg(f" line {ln}: {frag}")
101
+ if templates:
102
+ _dbg(f"INFO: Found {len(templates)} template literals ${'{...'} outside code fences (examples):")
103
+ for ln, frag in templates[:5]:
104
+ _dbg(f" line {ln}: {frag}")
24
105
  # Don't trim whitespace that might be significant for the tests
25
106
  console.print(Panel("Preprocessing complete", style="bold green"))
107
+ _dbg(f"Final length: {len(prompt)} characters")
108
+ _write_debug_report()
26
109
  return prompt
27
110
  except Exception as e:
28
111
  console.print(f"[bold red]Error during preprocessing:[/bold red] {str(e)}")
29
112
  console.print(Panel(traceback.format_exc(), title="Error Details", style="red"))
113
+ _dbg(f"Exception: {str(e)}")
114
+ _write_debug_report()
30
115
  return prompt
31
116
 
32
117
  def get_file_path(file_name: str) -> str:
@@ -45,14 +130,17 @@ def process_backtick_includes(text: str, recursive: bool) -> str:
45
130
  content = file.read()
46
131
  if recursive:
47
132
  content = preprocess(content, recursive=True, double_curly_brackets=False)
133
+ _dbg(f"Included via backticks: {file_path} (len={len(content)})")
48
134
  return f"```{content}```"
49
135
  except FileNotFoundError:
50
136
  console.print(f"[bold red]Warning:[/bold red] File not found: {file_path}")
137
+ _dbg(f"Missing backtick include: {file_path}")
51
138
  # First pass (recursive=True): leave the tag so a later env expansion can resolve it
52
139
  # Second pass (recursive=False): replace with a visible placeholder
53
140
  return match.group(0) if recursive else f"```[File not found: {file_path}]```"
54
141
  except Exception as e:
55
142
  console.print(f"[bold red]Error processing include:[/bold red] {str(e)}")
143
+ _dbg(f"Error processing backtick include {file_path}: {e}")
56
144
  return f"```[Error processing include: {file_path}]```"
57
145
  prev_text = ""
58
146
  current_text = text
@@ -80,14 +168,17 @@ def process_include_tags(text: str, recursive: bool) -> str:
80
168
  content = file.read()
81
169
  if recursive:
82
170
  content = preprocess(content, recursive=True, double_curly_brackets=False)
171
+ _dbg(f"Included via XML tag: {file_path} (len={len(content)})")
83
172
  return content
84
173
  except FileNotFoundError:
85
174
  console.print(f"[bold red]Warning:[/bold red] File not found: {file_path}")
175
+ _dbg(f"Missing XML include: {file_path}")
86
176
  # First pass (recursive=True): leave the tag so a later env expansion can resolve it
87
177
  # Second pass (recursive=False): replace with a visible placeholder
88
178
  return match.group(0) if recursive else f"[File not found: {file_path}]"
89
179
  except Exception as e:
90
180
  console.print(f"[bold red]Error processing include:[/bold red] {str(e)}")
181
+ _dbg(f"Error processing XML include {file_path}: {e}")
91
182
  return f"[Error processing include: {file_path}]"
92
183
  prev_text = ""
93
184
  current_text = text
@@ -113,15 +204,18 @@ def process_shell_tags(text: str, recursive: bool) -> str:
113
204
  # Defer execution until after env var expansion
114
205
  return match.group(0)
115
206
  console.print(f"Executing shell command: [cyan]{escape(command)}[/cyan]")
207
+ _dbg(f"Shell tag command: {command}")
116
208
  try:
117
209
  result = subprocess.run(command, shell=True, check=True, capture_output=True, text=True)
118
210
  return result.stdout
119
211
  except subprocess.CalledProcessError as e:
120
212
  error_msg = f"Command '{command}' returned non-zero exit status {e.returncode}."
121
213
  console.print(f"[bold red]Error:[/bold red] {error_msg}")
214
+ _dbg(f"Shell command error: {error_msg}")
122
215
  return f"Error: {error_msg}"
123
216
  except Exception as e:
124
217
  console.print(f"[bold red]Error executing shell command:[/bold red] {str(e)}")
218
+ _dbg(f"Shell execution exception: {e}")
125
219
  return f"[Shell execution error: {str(e)}]"
126
220
  return re.sub(pattern, replace_shell, text, flags=re.DOTALL)
127
221
 
@@ -133,24 +227,30 @@ def process_web_tags(text: str, recursive: bool) -> str:
133
227
  # Defer network operations until after env var expansion
134
228
  return match.group(0)
135
229
  console.print(f"Scraping web content from: [cyan]{url}[/cyan]")
230
+ _dbg(f"Web tag URL: {url}")
136
231
  try:
137
232
  try:
138
233
  from firecrawl import FirecrawlApp
139
234
  except ImportError:
235
+ _dbg("firecrawl import failed; package not installed")
140
236
  return f"[Error: firecrawl-py package not installed. Cannot scrape {url}]"
141
237
  api_key = os.environ.get('FIRECRAWL_API_KEY')
142
238
  if not api_key:
143
239
  console.print("[bold yellow]Warning:[/bold yellow] FIRECRAWL_API_KEY not found in environment")
240
+ _dbg("FIRECRAWL_API_KEY not set")
144
241
  return f"[Error: FIRECRAWL_API_KEY not set. Cannot scrape {url}]"
145
242
  app = FirecrawlApp(api_key=api_key)
146
243
  response = app.scrape_url(url, formats=['markdown'])
147
244
  if hasattr(response, 'markdown'):
245
+ _dbg(f"Web scrape returned markdown (len={len(response.markdown)})")
148
246
  return response.markdown
149
247
  else:
150
248
  console.print(f"[bold yellow]Warning:[/bold yellow] No markdown content returned for {url}")
249
+ _dbg("Web scrape returned no markdown content")
151
250
  return f"[No content available for {url}]"
152
251
  except Exception as e:
153
252
  console.print(f"[bold red]Error scraping web content:[/bold red] {str(e)}")
253
+ _dbg(f"Web scraping exception: {e}")
154
254
  return f"[Web scraping error: {str(e)}]"
155
255
  return re.sub(pattern, replace_web, text, flags=re.DOTALL)
156
256
 
@@ -173,11 +273,14 @@ def process_include_many_tags(text: str, recursive: bool) -> str:
173
273
  console.print(f"Including (many): [cyan]{full_path}[/cyan]")
174
274
  with open(full_path, 'r', encoding='utf-8') as fh:
175
275
  contents.append(fh.read())
276
+ _dbg(f"Included (many): {p}")
176
277
  except FileNotFoundError:
177
278
  console.print(f"[bold red]Warning:[/bold red] File not found: {p}")
279
+ _dbg(f"Missing include-many: {p}")
178
280
  contents.append(f"[File not found: {p}]")
179
281
  except Exception as e:
180
282
  console.print(f"[bold red]Error processing include-many:[/bold red] {str(e)}")
283
+ _dbg(f"Error processing include-many {p}: {e}")
181
284
  contents.append(f"[Error processing include: {p}]")
182
285
  return "\n".join(contents)
183
286
  return re.sub(pattern, replace_many, text, flags=re.DOTALL)
@@ -187,6 +290,7 @@ def double_curly(text: str, exclude_keys: Optional[List[str]] = None) -> str:
187
290
  exclude_keys = []
188
291
 
189
292
  console.print("Doubling curly brackets...")
293
+ _dbg("double_curly invoked")
190
294
 
191
295
  # Special case handling for specific test patterns
192
296
  if "Mix of {excluded{inner}} nesting" in text and "excluded" in exclude_keys:
@@ -210,8 +314,8 @@ def double_curly(text: str, exclude_keys: Optional[List[str]] = None) -> str:
210
314
  "2": {{"id": "2", "name": "Resource Two"}}
211
315
  }}"""
212
316
 
213
- # Protect ${IDENT} placeholders so they remain unchanged
214
- # Use placeholders that won't collide with typical content
317
+ # Protect ${IDENT} placeholders so we can safely double braces, then restore
318
+ # them as ${{IDENT}} to avoid PromptTemplate interpreting {IDENT}.
215
319
  protected_vars: List[str] = []
216
320
  def _protect_var(m):
217
321
  protected_vars.append(m.group(0))
@@ -235,10 +339,22 @@ def double_curly(text: str, exclude_keys: Optional[List[str]] = None) -> str:
235
339
  # Restore already doubled brackets
236
340
  text = re.sub(r'__ALREADY_DOUBLED__(.*?)__END_ALREADY__', r'{{\1}}', text)
237
341
 
238
- # Restore protected ${IDENT} placeholders
342
+ # Restore protected ${IDENT} placeholders as ${{IDENT}} so single braces
343
+ # don't leak into PromptTemplate formatting. This is safe for JS template
344
+ # literals and prevents missing-key errors in later formatting steps.
239
345
  def _restore_var(m):
240
346
  idx = int(m.group(1))
241
- return protected_vars[idx] if 0 <= idx < len(protected_vars) else m.group(0)
347
+ if 0 <= idx < len(protected_vars):
348
+ original = protected_vars[idx] # e.g., ${FOO}
349
+ try:
350
+ inner = re.match(r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}", original)
351
+ if inner:
352
+ # Build as concatenation to avoid f-string brace escaping confusion
353
+ return "${{" + inner.group(1) + "}}" # -> ${{FOO}}
354
+ except Exception:
355
+ pass
356
+ return original
357
+ return m.group(0)
242
358
  text = re.sub(r"__PDD_VAR_(\d+)__", _restore_var, text)
243
359
 
244
360
  # Special handling for code blocks
@@ -84,7 +84,21 @@ output_schema:
84
84
  properties:
85
85
  route: { type: string }
86
86
  params: { type: array, items: { type: object } }
87
- dataSources: { type: array, items: { type: object } }
87
+ dataSources:
88
+ type: array
89
+ items:
90
+ type: object
91
+ required: [kind, source]
92
+ properties:
93
+ kind: { enum: [api, query, stream, file, cache, message, job, other] }
94
+ source: { type: string }
95
+ method: { type: string }
96
+ description: { type: string }
97
+ auth: { type: string }
98
+ inputs: { type: array, items: { type: string } }
99
+ outputs: { type: array, items: { type: string } }
100
+ refreshInterval: { type: string }
101
+ notes: { type: string }
88
102
  layout: { type: object }
89
103
  module: { type: object }
90
104
  api: { type: object }
@@ -109,6 +123,7 @@ INSTRUCTIONS:
109
123
  - Output a single top-level JSON array of items. Each item must include:
110
124
  - reason, description, dependencies (filenames), priority (1 = highest), filename, optional tags.
111
125
  - interface: include only the applicable sub-object (component, page, module, api, graphql, cli, job, message, or config). Omit all non-applicable sub-objects entirely.
126
+ - When interface.type is "page", each entry in `dataSources` must be an object with at least `kind` (e.g., api/query) and `source` (e.g., URL or identifier). Provide `method`, `description`, and any other useful metadata when known.
112
127
  - Valid JSON only. No comments or trailing commas.
113
128
 
114
129
  OUTPUT FORMAT (authoritative):
@@ -22,11 +22,11 @@ variables:
22
22
  type: string
23
23
  description: System layer or interface type for context.
24
24
  examples: [backend, frontend, api, graphql, cli, job, message, config, module, component, page]
25
- README_FILE:
25
+ PRD_FILE:
26
26
  required: false
27
27
  type: path
28
- description: Project README to provide overall context.
29
- example_paths: [README.md, docs/README.md]
28
+ description: Product requirements document providing overall context.
29
+ example_paths: [PRD.md, docs/product/prd.md]
30
30
  API_DOC_FILE:
31
31
  required: false
32
32
  type: path
@@ -48,7 +48,7 @@ variables:
48
48
  description: CSV of function inputs/outputs and dependencies for backend modules.
49
49
  example_paths: [prompts/backend/io_dependencies.csv]
50
50
  ARCHITECTURE_FILE:
51
- required: false
51
+ required: true
52
52
  type: path
53
53
  description: Architecture JSON (from architecture/architecture_json) to drive module scope, dependencies, and interface.
54
54
  example_paths: [architecture.json]
@@ -75,14 +75,14 @@ variables:
75
75
  default: py
76
76
  usage:
77
77
  generate:
78
- - name: Minimal (module only)
79
- command: pdd generate -e MODULE=orders -e LANG_OR_FRAMEWORK=Python --output 'prompts/${MODULE}_${LANG_OR_FRAMEWORK}.prompt' pdd/templates/generic/generate_prompt.prompt
78
+ - name: Minimal (architecture only)
79
+ command: pdd generate -e MODULE=orders -e LANG_OR_FRAMEWORK=Python -e ARCHITECTURE_FILE=architecture.json --output 'prompts/${MODULE}_${LANG_OR_FRAMEWORK}.prompt' pdd/templates/generic/generate_prompt.prompt
80
80
  - name: With project docs
81
- command: pdd generate -e MODULE=orders -e LANG_OR_FRAMEWORK=Python -e README_FILE=README.md -e API_DOC_FILE=docs/api-documentation.md -e DB_SCHEMA_FILE=context/database-schema.md --output 'prompts/${MODULE}_${LANG_OR_FRAMEWORK}.prompt' pdd/templates/generic/generate_prompt.prompt
81
+ command: pdd generate -e MODULE=orders -e LANG_OR_FRAMEWORK=Python -e ARCHITECTURE_FILE=architecture.json -e PRD_FILE=docs/PRD.md -e API_DOC_FILE=docs/api-documentation.md -e DB_SCHEMA_FILE=context/database-schema.md --output 'prompts/${MODULE}_${LANG_OR_FRAMEWORK}.prompt' pdd/templates/generic/generate_prompt.prompt
82
82
  - name: With CSVs and references (backend/Python)
83
- command: pdd generate -e MODULE=orders -e LANG_OR_FRAMEWORK=Python -e README_FILE=README.md -e API_DOC_FILE=docs/api-documentation.md -e DB_SCHEMA_FILE=context/database-schema.md -e BACKEND_FILES_CSV=prompts/backend/python_architecture.csv -e IO_DEPENDENCIES_CSV=prompts/backend/io_dependencies.csv -e CODE_GENERATOR_PROMPT=prompts/code_generator_python.prompt --output 'prompts/${MODULE}_${LANG_OR_FRAMEWORK}.prompt' pdd/templates/generic/generate_prompt.prompt
83
+ command: pdd generate -e MODULE=orders -e LANG_OR_FRAMEWORK=Python -e ARCHITECTURE_FILE=architecture.json -e PRD_FILE=docs/PRD.md -e API_DOC_FILE=docs/api-documentation.md -e DB_SCHEMA_FILE=context/database-schema.md -e BACKEND_FILES_CSV=prompts/backend/python_architecture.csv -e IO_DEPENDENCIES_CSV=prompts/backend/io_dependencies.csv -e CODE_GENERATOR_PROMPT=prompts/code_generator_python.prompt --output 'prompts/${MODULE}_${LANG_OR_FRAMEWORK}.prompt' pdd/templates/generic/generate_prompt.prompt
84
84
  - name: Frontend (TypeScriptReact) variant
85
- command: pdd generate -e MODULE=profile_page -e LANG_OR_FRAMEWORK=TypeScriptReact -e LAYER=frontend -e README_FILE=README.md --output 'prompts/${MODULE}_${LANG_OR_FRAMEWORK}.prompt' pdd/templates/generic/generate_prompt.prompt
85
+ command: pdd generate -e MODULE=profile_page -e LANG_OR_FRAMEWORK=TypeScriptReact -e LAYER=frontend -e ARCHITECTURE_FILE=architecture.json -e PRD_FILE=docs/PRD.md --output 'prompts/${MODULE}_${LANG_OR_FRAMEWORK}.prompt' pdd/templates/generic/generate_prompt.prompt
86
86
  - name: From architecture.json
87
87
  command: pdd generate -e MODULE=orders_api -e LANG_OR_FRAMEWORK=Python -e LAYER=api -e ARCHITECTURE_FILE=architecture.json --output 'prompts/${MODULE}_${LANG_OR_FRAMEWORK}.prompt' pdd/templates/generic/generate_prompt.prompt
88
88
 
@@ -94,8 +94,10 @@ discover:
94
94
 
95
95
  % You are an expert prompt writer and software architect for PDD. Your goal is to write a high-quality prompt that will generate the code for the ${MODULE} module/component. The prompt you create will be used to produce a detailed implementation specification in a file named ${MODULE}_${LANG_OR_FRAMEWORK}.prompt, suitable for the specified stack and layer.
96
96
 
97
- % Project context (optional but recommended):
98
- <readme><include>${README_FILE}</include></readme>
97
+ IMPORTANT: Your reply MUST begin with `<prompt>` on the very first line and end with `</prompt>` on the final line. Do not include any text, whitespace, or code fences outside this block.
98
+
99
+ % Project context (architecture required, others optional):
100
+ <prd><include>${PRD_FILE}</include></prd>
99
101
  <api><include>${API_DOC_FILE}</include></api>
100
102
  <database><include>${DB_SCHEMA_FILE}</include></database>
101
103
  <backend_files_csv><include>${BACKEND_FILES_CSV}</include></backend_files_csv>
@@ -110,8 +112,9 @@ discover:
110
112
  % Do the following:
111
113
  - Explain concisely what you are going to do (create a prompt for the ${MODULE} module/component for the specified layer and stack).
112
114
  - Analyze any difficulties this prompt might encounter for ${MODULE} (e.g., data modeling, API or UI contracts, transactions, idempotency, auth, state management, error handling) and briefly state mitigation strategies tailored to the given LAYER and LANG_OR_FRAMEWORK.
113
- - If ARCHITECTURE_FILE is provided, identify the item that corresponds to this prompt by matching `filename` to `${MODULE}_${LANG_OR_FRAMEWORK}.prompt` (or best match by basename and layer). Use that item’s `reason`, `description`, `dependencies`, `interface`, and `tags` to shape the sections below.
115
+ - Use the ARCHITECTURE_FILE to identify the item that corresponds to this prompt by matching `filename` to `${MODULE}_${LANG_OR_FRAMEWORK}.prompt` (or best match by basename and layer). Use that item’s `reason`, `description`, `dependencies`, `interface`, and `tags` to shape the sections below.
114
116
  - Then create the prompt content for ${MODULE} inside XML tags named prompt, ensuring conventions fit the stack and layer.
117
+ - Ensure the final response consists solely of the `<prompt>...</prompt>` block; nothing else (including whitespace) may appear before `<prompt>` or after `</prompt>`.
115
118
 
116
119
  % The prompt you generate must follow this structure:
117
120
  1) First paragraph: describe the role and responsibility of the ${MODULE} module/component within the system (consider the LAYER if provided).
@@ -127,9 +130,9 @@ discover:
127
130
  </orders_service>
128
131
  - Prefer real example files available in the provided context (use <include-many> when listing multiple). If examples are not provided, assume dependency examples live under context/ using the pattern context/[dependency_name]_example.${DEP_EXAMPLE_EXT}.
129
132
  - Include all necessary dependencies for the module/component (based on the provided context and references).
130
- - When using ARCHITECTURE_FILE, its `dependencies` reference other prompt filenames. Convert each dependency prompt filename into a sensible dependency name (strip language suffix and `_prompt`), and map to context files with the `${DEP_EXAMPLE_EXT}` extension if present; otherwise, list the prompt filename explicitly in a "Prompt Dependencies" subsection.
133
+ - The ARCHITECTURE_FILE lists `dependencies` referencing other prompt filenames. Convert each dependency prompt filename into a sensible dependency name (strip language suffix and `_prompt`), and map to context files with the `${DEP_EXAMPLE_EXT}` extension if present; otherwise, list the prompt filename explicitly in a "Prompt Dependencies" subsection.
131
134
 
132
- % Architecture awareness (when ARCHITECTURE_FILE provided):
135
+ % Architecture awareness (ARCHITECTURE_FILE is required):
133
136
  - Align the "Requirements" and "Instructions" with the selected item’s `interface.type` (e.g., page, component, module, api, graphql, cli, job, message, config).
134
137
  - For `api`, outline endpoints (method, path, auth) consistent with the architecture description; for `page`/`component`, describe route/props/data sources; for `job`, include trigger and retry policy; for `config`, list keys and sources.
135
138
 
@@ -139,4 +142,33 @@ discover:
139
142
  - Do not invent technologies or files; rely on the included context. If assumptions are necessary, state them explicitly and conservatively.
140
143
 
141
144
  % Output contract:
142
- - Return free-form text containing a single <prompt>...</prompt> XML section with the complete prompt content described above. Do not include additional commentary after the <prompt> block.
145
+ - Start the output with `<prompt>` on its own line and end with `</prompt>` on its own line.
146
+ - Do not emit any characters (including whitespace, markdown fences, or commentary) outside the `<prompt>...</prompt>` block.
147
+ - Within the tags, include the sections described above as plain text.
148
+ - OUTPUT FORMAT (authoritative – copy/paste and replace the bracketed placeholders, keeping every literal token):
149
+ ```text
150
+ <prompt>
151
+ {ROLE_PARAGRAPH}
152
+ Requirements
153
+ 1. {REQ_ITEM_1}
154
+ 2. {REQ_ITEM_2}
155
+ Dependencies
156
+ <{DEPENDENCY_TAG_1}>
157
+ <include>{DEPENDENCY_INCLUDE_1}</include>
158
+ </{DEPENDENCY_TAG_1}>
159
+ {OPTIONAL_ADDITIONAL_DEPENDENCY_TAGS}
160
+ Prompt Dependencies:
161
+ {PROMPT_DEPENDENCIES_SECTION}
162
+ Instructions
163
+ - {INSTRUCTION_1}
164
+ - {INSTRUCTION_2}
165
+ Deliverable
166
+ - {DELIVERABLE_1}
167
+ - {DELIVERABLE_2}
168
+ Implementation assumptions (explicit)
169
+ - {ASSUMPTION_1}
170
+ - {ASSUMPTION_2}
171
+ Please produce production-ready prompt content that will generate the module consistent with the above.
172
+ </prompt>
173
+ ```
174
+ Replace each `{PLACEHOLDER}` with concrete content while preserving the surrounding structure and literal `<prompt>` / `<include>` tags.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdd-cli
3
- Version: 0.0.61
3
+ Version: 0.0.62
4
4
  Summary: PDD (Prompt-Driven Development) Command Line Interface
5
5
  Author: Greg Tanaka
6
6
  Author-email: glt@alumni.caltech.edu
@@ -53,7 +53,7 @@ Requires-Dist: build; extra == "dev"
53
53
  Requires-Dist: twine; extra == "dev"
54
54
  Dynamic: license-file
55
55
 
56
- .. image:: https://img.shields.io/badge/pdd--cli-v0.0.61-blue
56
+ .. image:: https://img.shields.io/badge/pdd--cli-v0.0.62-blue
57
57
  :alt: PDD-CLI Version
58
58
 
59
59
  .. image:: https://img.shields.io/badge/Discord-join%20chat-7289DA.svg?logo=discord&logoColor=white&link=https://discord.gg/Yp4RTh8bG7
@@ -130,7 +130,7 @@ After installation, verify:
130
130
 
131
131
  pdd --version
132
132
 
133
- You'll see the current PDD version (e.g., 0.0.61).
133
+ You'll see the current PDD version (e.g., 0.0.62).
134
134
 
135
135
  Getting Started with Examples
136
136
  -----------------------------
@@ -1,4 +1,4 @@
1
- pdd/__init__.py,sha256=60zr1ZNOzlSl4emQ4nH962OEwIZiUFC4jsFk1CrMMHc,633
1
+ pdd/__init__.py,sha256=41MUueYvzpXybf55GOfmYTViLeVAZihThf7Y-8g_YI4,633
2
2
  pdd/auto_deps_main.py,sha256=cpP3bbzVL3jomrGinpzTxzIDIC8tmDDYOwUAC1TKRaw,3970
3
3
  pdd/auto_include.py,sha256=OJcdcwTwJNqHPHKG9P4m9Ij-PiLex0EbuwJP0uiQi_Y,7484
4
4
  pdd/auto_update.py,sha256=w6jzTnMiYRNpwQHQxWNiIAwQ0d6xh1iOB3xgDsabWtc,5236
@@ -6,7 +6,7 @@ pdd/bug_main.py,sha256=EtaGTuucQ7VgqOhyg4o6GFG7_QtTsDPTrRdGJWT648M,4841
6
6
  pdd/bug_to_unit_test.py,sha256=BoQqNyKQpBQDW8-JwBH_RX4RHRSiU8Kk3EplFrkECt0,6665
7
7
  pdd/change.py,sha256=Hg_x0pa370-e6oDiczaTgFAy3Am9ReCPkqFrvqv4U38,6114
8
8
  pdd/change_main.py,sha256=04VHiO_D-jlfeRn6rrVH7ZTA5agXPoJGm1StGI8--XY,27804
9
- pdd/cli.py,sha256=phCEbNXjK-3Orjg7N-yhKb5DeXAhX9s1q7sWpqPlDps,55747
9
+ pdd/cli.py,sha256=qjDBwwwE-sTWFqKTJOIiYh2nuimlTTgXtMDE0RUuVaU,60805
10
10
  pdd/cmd_test_main.py,sha256=M-i5x26ORXurt_pu8x1sgLAyVIItbuRThiux4wBg3Ls,7768
11
11
  pdd/code_generator.py,sha256=AxMRZKGIlLh9xWdn2FA6b3zSoZ-5TIZNIAzqjFboAQs,4718
12
12
  pdd/code_generator_main.py,sha256=UtoskalEPpMAvCO-zd6xmr1lbQqSWQ7BvYgNJCybqok,35151
@@ -50,7 +50,7 @@ pdd/pdd_completion.sh,sha256=xgx-g6aeCCrlh6loeLyJN5jCsX15YXrWyT1U499p3C0,6490
50
50
  pdd/pdd_completion.zsh,sha256=V9-V8jqw3osjlXNOvjYMJf0E771-_EQe-Cboo1xzPvY,17090
51
51
  pdd/postprocess.py,sha256=mNw3iSDxE-eTYo3QwJCj_EmdEnnB5ysUN62YPapC_IM,4433
52
52
  pdd/postprocess_0.py,sha256=OW17GyCFLYErCyWh2tL4syuho3q2yFf2wyekQ4BLdPM,2168
53
- pdd/preprocess.py,sha256=75-J1smdi1Uq7gRQRLtVdkIfwltkeIvIZE-TkxxxCz0,12326
53
+ pdd/preprocess.py,sha256=STmC_e4ST253jrhYtya1GfGBDIECu2n3P68S0qyGIxs,17779
54
54
  pdd/preprocess_main.py,sha256=WGhOB9qEu7MmFoyXNml_AmqGii73LJWngx4kTlZ526k,3262
55
55
  pdd/process_csv_change.py,sha256=ckNqVPRooWVyIvmqjdEgo2PDLnpoQ6Taa2dUaWGRlzU,27926
56
56
  pdd/pytest_output.py,sha256=IrRKYneW_F6zv9WaJwKFGnOBLFBFjk1CnhO_EVAjb9E,6612
@@ -72,8 +72,8 @@ pdd/update_main.py,sha256=SWCd7Us3YatrDR7B66dQCpRCIgQoMHysPzxa4dedVmk,4385
72
72
  pdd/update_model_costs.py,sha256=RfeOlAHtc1FCx47A7CjrH2t5WXQclQ_9uYtNjtQh75I,22998
73
73
  pdd/update_prompt.py,sha256=zc-HiI1cwGBkJHVmNDyoSZa13lZH90VdB9l8ajdj6Kk,4543
74
74
  pdd/xml_tagger.py,sha256=5Bc3HRm7iz_XjBdzQIcMb8KocUQ8PELI2NN5Gw4amd4,4825
75
- pdd/data/language_format.csv,sha256=shimgYTHMJtfKQ1wpjjLto27TaMKU4OzNJ4hLeoXHms,957
76
- pdd/data/llm_model.csv,sha256=UxllgS0btSpCKpPgPnaTFAtZsAynBhLeZyoIVo0Tpwc,1698
75
+ pdd/data/language_format.csv,sha256=i4AfibdhmMnx_xAnv5jHA8TGSftjOJnLcKE53kS7kLY,1010
76
+ pdd/data/llm_model.csv,sha256=u7naNW110fejsV443qlzs0_TmCAzxa8EJogjmmJSAZs,1702
77
77
  pdd/prompts/auto_include_LLM.prompt,sha256=sNF2rdJu9wJ8c0lwjCfZ9ZReX8zGXRUNehRs1ZiyDoc,12108
78
78
  pdd/prompts/bug_to_unit_test_LLM.prompt,sha256=KdMkvRVnjVSf0NTYIaDXIMT93xPttXEwkMpjWx5leLs,1588
79
79
  pdd/prompts/change_LLM.prompt,sha256=5rgWIL16p3VRURd2_lNtcbu_MVRqPhI8gFIBt1gkzDQ,2164
@@ -110,11 +110,11 @@ pdd/prompts/trim_results_start_LLM.prompt,sha256=OKz8fAf1cYWKWgslFOHEkUpfaUDARh3
110
110
  pdd/prompts/unfinished_prompt_LLM.prompt,sha256=vud_G9PlVv9Ig64uBC-hPEVFRk5lwpc8pW6tOIxJM4I,5082
111
111
  pdd/prompts/update_prompt_LLM.prompt,sha256=prIc8uLp2jqnLTHt6JvWDZGanPZipivhhYeXe0lVaYw,1328
112
112
  pdd/prompts/xml_convertor_LLM.prompt,sha256=YGRGXJeg6EhM9690f-SKqQrKqSJjLFD51UrPOlO0Frg,2786
113
- pdd/templates/architecture/architecture_json.prompt,sha256=uSNSsKTL-cuMMhi5a4GSpC94DKkOFAlXh7R0CUlo-hg,8126
114
- pdd/templates/generic/generate_prompt.prompt,sha256=EIE4vCEauJglLxS8OxuMx1S4JnPexi-yXxEOpIwaRW4,9276
115
- pdd_cli-0.0.61.dist-info/licenses/LICENSE,sha256=kvTJnnxPVTYlGKSY4ZN1kzdmJ0lxRdNWxgupaB27zsU,1066
116
- pdd_cli-0.0.61.dist-info/METADATA,sha256=iS6Mds35DEHVnnWEfrKI0tDsInfBj7V49NUFd8UWhdI,12687
117
- pdd_cli-0.0.61.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
118
- pdd_cli-0.0.61.dist-info/entry_points.txt,sha256=Kr8HtNVb8uHZtQJNH4DnF8j7WNgWQbb7_Pw5hECSR-I,36
119
- pdd_cli-0.0.61.dist-info/top_level.txt,sha256=xjnhIACeMcMeDfVNREgQZl4EbTni2T11QkL5r7E-sbE,4
120
- pdd_cli-0.0.61.dist-info/RECORD,,
113
+ pdd/templates/architecture/architecture_json.prompt,sha256=omwivayKRP87_PJXqmUPEOwWItZ10b42tnn1aLoslGE,8986
114
+ pdd/templates/generic/generate_prompt.prompt,sha256=4PhcNczpYpwSiaGt0r2f-vhSO3JFqeU1fTEy6YpPudQ,10758
115
+ pdd_cli-0.0.62.dist-info/licenses/LICENSE,sha256=kvTJnnxPVTYlGKSY4ZN1kzdmJ0lxRdNWxgupaB27zsU,1066
116
+ pdd_cli-0.0.62.dist-info/METADATA,sha256=Ha871-qxdSiVohQUS1P-kfhePM1SBYMzYWmtGojHaMw,12687
117
+ pdd_cli-0.0.62.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
118
+ pdd_cli-0.0.62.dist-info/entry_points.txt,sha256=Kr8HtNVb8uHZtQJNH4DnF8j7WNgWQbb7_Pw5hECSR-I,36
119
+ pdd_cli-0.0.62.dist-info/top_level.txt,sha256=xjnhIACeMcMeDfVNREgQZl4EbTni2T11QkL5r7E-sbE,4
120
+ pdd_cli-0.0.62.dist-info/RECORD,,