Skip to content

Conversation

@frankbria
Copy link
Owner

@frankbria frankbria commented Jan 31, 2026

Summary

Implements a PRD template system that allows users to customize the output format of generated PRDs. This addresses issue #316.

  • 5 built-in templates: standard, lean, enterprise, user-story-map, technical-spec
  • Jinja2 rendering: Powerful templating with custom filters (bullet_list, numbered_list, table)
  • YAML storage: Human-readable template files for easy customization
  • CLI commands: List, show, import, export, and generate with templates

New CLI Commands

cf prd templates list                    # List available templates
cf prd templates show <id>               # Show template details  
cf prd templates import <file>           # Import custom template
cf prd templates export <id> <output>    # Export template to YAML
cf prd generate --template <id>          # Generate PRD with template

Built-in Templates

Template Sections Use Case
standard 7 Default full PRD format
lean 3 MVP/quick iterations
enterprise 10 Formal PRD with compliance
user-story-map 4 Agile user journey focus
technical-spec 6 Technical audience

Files Changed

  • codeframe/planning/prd_templates.py - Core template system
  • codeframe/planning/prd_template_functions.py - Jinja2 custom filters
  • codeframe/cli/app.py - CLI commands for template management
  • codeframe/planning/__init__.py - Module exports
  • pyproject.toml / uv.lock - Added jinja2 dependency

Test Plan

  • 32 unit tests for template core (data structures, rendering, validation, storage)
  • 12 CLI tests for template commands
  • All 1541 existing tests pass (no regressions)
  • Ruff linting passes
# Run new tests
uv run pytest tests/planning/test_prd_templates.py tests/cli/test_prd_template_commands.py -v

# Demo commands
uv run codeframe prd templates list
uv run codeframe prd templates show lean

Closes #316

Summary by CodeRabbit

  • New Features

    • PRD template system with built-in and custom templates, rendering helpers, upfront validation, and template-aware PRD generation (selectable via --template; selection logged and recorded).
    • CLI subcommands to list, show, import, and export templates.
  • Tests

    • End-to-end CLI tests and extensive unit/integration tests covering templates, rendering helpers, import/export, persistence, and generation flows.
  • Chores

    • Added Jinja2 dependency.

✏️ Tip: You can customize this high-level summary in your review settings.

…ts (#316)

Implements a template system for PRD generation:

- Add PrdTemplateSection and PrdTemplate dataclasses for template structure
- Add PrdTemplateManager for template loading, validation, and rendering
- Create 5 built-in templates: standard, lean, enterprise, user-story-map, technical-spec
- Implement Jinja2-based template rendering with custom filters (bullet_list, numbered_list, table)
- Add YAML serialization for template import/export

CLI commands:
- `cf prd templates list` - List available templates
- `cf prd templates show <id>` - Show template details
- `cf prd templates import <file>` - Import custom template
- `cf prd templates export <id> <output>` - Export template to YAML
- `cf prd generate --template <id>` - Generate PRD using specific template

Templates support:
- Global templates in ~/.codeframe/templates/prd/
- Project templates in .codeframe/templates/prd/
- Template validation (required fields, Jinja2 syntax)
- Custom template functions for list formatting

Tests: 44 new tests covering template system and CLI commands
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 31, 2026

Walkthrough

Adds a PRD template system (models, manager, built-ins, Jinja2 helpers), CLI subcommands to list/show/export/import templates, integrates template selection into prd generate with upfront validation and template-driven prompt building, and adds tests plus a Jinja2 dependency.

Changes

Cohort / File(s) Summary
CLI: PRD template commands
codeframe/cli/app.py
Adds prd templates Typer sub-app (list, show, export, import), updates prd_generate signature to accept --template, validates templates via PrdTemplateManager, logs selection, and prevents generation with unknown templates.
Template models & manager
codeframe/planning/prd_templates.py
New PrdTemplateSection/PrdTemplate dataclasses, BUILTIN_TEMPLATES, filesystem I/O helpers, and PrdTemplateManager with load/list/get/validate/render/import/export/register plus per-section Jinja2 rendering and error handling.
Template helpers
codeframe/planning/prd_template_functions.py
Adds Jinja2 helper functions (bullet_list, numbered_list, table, summarize, join_list, format_constraints) and TEMPLATE_FUNCTIONS registry for use during rendering.
Public exports
codeframe/planning/__init__.py
Re-exports PrdTemplate, PrdTemplateSection, PrdTemplateManager, BUILTIN_TEMPLATES to expose the template API.
Core PRD flow
codeframe/core/prd_discovery.py
generate_prd now accepts template_id; adds _build_prd_prompt to construct template-driven prompts via PrdTemplateManager; stores template_id in PRD metadata and records requested template when missing.
Dependency
pyproject.toml
Adds jinja2>=3.1.6 dependency for template rendering.
Tests
tests/cli/test_prd_template_commands.py, tests/planning/test_prd_templates.py
Adds CLI and unit tests covering template list/show/export/import, validation, rendering behavior, built-ins, helper functions, persistence, and integration with PRD generation flows.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant CLI as CLI (app.py)
    participant Manager as PrdTemplateManager
    participant Jinja as Jinja2 Engine
    participant Discovery as Discovery Session
    participant FS as File System

    User->>CLI: prd templates list
    CLI->>Manager: list_templates()
    Manager-->>CLI: templates metadata
    CLI-->>User: display templates

    User->>CLI: prd generate --template <id>
    CLI->>Manager: get_template(<id>)
    alt template exists
        Manager-->>CLI: template
        CLI->>Discovery: run discovery -> gather discovery_data
        Discovery-->>CLI: discovery_data
        CLI->>Manager: render_template(template, discovery_data)
        loop per section
            Manager->>Jinja: render section with discovery_data + TEMPLATE_FUNCTIONS
            Jinja-->>Manager: rendered section or error placeholder
        end
        Manager-->>CLI: combined PRD markdown
        CLI->>FS: save PRD (include template_id metadata)
        CLI-->>User: output/save PRD
    else missing template
        Manager-->>CLI: not found
        CLI-->>User: error + list available templates
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐇
I hopped through Jinja, stitched each section bright,
YAML and templates turned discovery to light.
I munched on bugs, stitched defaults tight,
Sections lined up, markdown danced in sight.
Hop on—plans now take flight.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a PRD template system for customizable output formats, which aligns with the primary objective across all modified files.
Linked Issues check ✅ Passed The PR implements all major requirements from issue #316: YAML-based template DSL, template variables, Jinja2 functions, five built-in templates, global/project storage, validation, and CLI integration with cf prd templates commands.
Out of Scope Changes check ✅ Passed All changes directly support the PRD template system implementation with no unrelated modifications to unrelated components or features.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/prd-templates-316

Comment @coderabbitai help to get the list of available commands and usage tips.

@macroscopeapp
Copy link
Contributor

macroscopeapp bot commented Jan 31, 2026

Add PRD template system and route codeframe prd generate to PrdDiscoverySession.generate_prd(template_id) with CLI management commands in app.py

Introduce Jinja2‑based PRD templates with built‑ins and disk discovery, add prd templates CLI for list/show/export/import, and update PrdDiscoverySession.generate_prd to accept template_id and build prompts via _build_prd_prompt in prd_discovery.py.

📍Where to Start

Start with the prd_app and prd_templates_app command definitions in app.py, then review PrdDiscoverySession.generate_prd and _build_prd_prompt in prd_discovery.py.


Macroscope summarized 8ed3e90.

@claude
Copy link

claude bot commented Jan 31, 2026

Code Review: PRD Template System (#316)

I've completed a thorough review of this PR. Overall, this is a well-architected feature with solid foundations, but there's a critical integration gap that prevents the templates from being used in PRD generation. Here's my detailed feedback:


🚨 Critical Issue: Template Not Actually Used

The template is validated but never passed to the PRD generation logic.

In codeframe/cli/app.py:1288-1433:

  • Line 1289-1296: Template is validated and retrieved ✓
  • Line 1298: User is informed which template will be used ✓
  • Line 1433: session.generate_prd() is called without the template

Impact: The --template flag is effectively a no-op. All PRDs use the default format regardless of the flag value.

Required fix:

  1. Add template_id parameter to PrdDiscoverySession.__init__() or generate_prd()
  2. Pass the template object to the session: session.generate_prd(template=template_obj)
  3. Update PrdDiscoverySession.generate_prd() to use PrdTemplateManager.render_template()

✅ Strengths

Architecture & Design

  • Clean separation of concerns: Template data structures, rendering logic, and Jinja2 filters are well-separated
  • Proper use of dataclasses: PrdTemplateSection and PrdTemplate are clean and type-safe
  • YAML storage: Human-readable format makes templates easy to customize
  • Safe YAML loading: Uses yaml.safe_load() (line 707) - good security practice ✓

Code Quality

  • Comprehensive validation: validate_template() checks required fields and Jinja2 syntax
  • Error handling: Proper exception handling in template rendering (lines 849-855)
  • Logging: Good use of debug/info/warning levels throughout
  • Custom Jinja2 filters: Clean implementation with proper type hints

Testing

  • 44 new tests covering core functionality and CLI commands
  • Good test organization: Separate test classes for each component
  • Edge cases covered: Missing templates, invalid Jinja2, empty sections

🔧 Code Quality Issues

1. Missing Error Handling in load_template_from_file (lines 693-726)

sections = [
    PrdTemplateSection(
        id=s["id"],  # KeyError if 'id' missing
        title=s["title"],  # KeyError if 'title' missing
        ...
    )
    for s in data.get("sections", [])
]

Issue: Malformed YAML will cause KeyError instead of a helpful validation error.

Recommendation:

try:
    sections = [
        PrdTemplateSection(
            id=s.get("id", ""),
            title=s.get("title", ""),
            ...
        )
        for s in data.get("sections", [])
    ]
    # Then validate the loaded template
    errors = manager.validate_template(template)
    if errors:
        raise ValueError(f"Invalid template: {', '.join(errors)}")
except (KeyError, TypeError) as e:
    raise ValueError(f"Malformed template file: {e}")

2. Path Traversal Vulnerability in import_template (codeframe/cli/app.py:706-716)

template = manager.import_template(source_path)
# No validation that source_path is safe

Issue: User can provide arbitrary file paths (e.g., ../../etc/passwd).

Recommendation: Add path validation or catch the exception with a user-friendly message:

try:
    if not source_path.exists():
        console.print(f"[red]Error:[/red] File not found: {source_path}")
        raise typer.Exit(1)
    if not source_path.is_file():
        console.print(f"[red]Error:[/red] Not a file: {source_path}")
        raise typer.Exit(1)
    template = manager.import_template(source_path)
except (PermissionError, OSError) as e:
    console.print(f"[red]Error:[/red] Cannot read file: {e}")
    raise typer.Exit(1)

3. Silent Failure in Template Loading (codeframe/planning/prd_templates.py:776-782)

for path in directory.glob("*.yaml"):
    try:
        template = load_template_from_file(path)
        self.templates[template.id] = template
        logger.debug(f"Loaded template from {path}")
    except Exception as e:
        logger.warning(f"Failed to load template from {path}: {e}")

Issue: Invalid custom templates are silently ignored. Users may not realize their template isn't being used.

Recommendation: Consider making this more visible:

  • Use logger.error() instead of logger.warning() for template load failures
  • Or add a --strict mode that fails on invalid templates

4. Template Collision Not Handled (line 778)

self.templates[template.id] = template

Issue: If a custom template has the same ID as a built-in, it silently overrides with no warning.

Recommendation: Add logging when overriding:

if template.id in self.templates:
    logger.info(f"Custom template '{template.id}' overriding built-in template")
self.templates[template.id] = template

📊 Performance Considerations

Jinja2 Environment Initialization (line 761)

self._env = Environment(loader=BaseLoader())

Minor: BaseLoader is fine for small templates, but if you later add template inheritance, consider using DictLoader or FileSystemLoader.

Not a blocking issue - current approach is appropriate for the scope.


🔒 Security Review

✅ Good Practices

  • Uses yaml.safe_load() instead of yaml.load() (prevents code execution)
  • Validates Jinja2 syntax before rendering (catches malicious templates)
  • No direct eval() or exec() calls

⚠️ Potential Concerns

  1. Jinja2 Sandboxing: Not using SandboxedEnvironment

    • Risk: If users import untrusted templates, they could contain malicious Jinja2 code
    • Severity: Low (users must explicitly import templates)
    • Recommendation: Consider using jinja2.sandbox.SandboxedEnvironment for defense-in-depth
  2. File System Access: Template import/export has limited validation (see issue Brainstorming: Integrate remaining general concepts into specification #2 above)


🧪 Test Coverage

What's Tested Well

  • Template data structures ✓
  • Manager initialization and template retrieval ✓
  • Validation logic ✓
  • Rendering with discovery data ✓
  • CLI commands ✓

Missing Tests

  1. No test for template actually being used in PRD generation
    • This would have caught the critical integration bug!
  2. No test for malformed YAML files (only tests invalid Jinja2)
  3. No test for custom filter edge cases (e.g., table() with empty columns)
  4. No test for template directory creation (lines 685)

📝 Documentation & Usability

Strengths

  • Clear docstrings for all public functions ✓
  • Good CLI help text with examples ✓
  • PR description includes comprehensive usage guide ✓

Suggestions

  1. Add a template authoring guide: How to create custom templates, available filters, etc.
  2. Document the source field: What values are valid? (problem, users, features, etc.)
  3. Add schema validation: Consider a JSON schema for template YAML files

🎯 Recommendations Summary

Must Fix (Blocking)

  1. Wire template to PRD generation - Currently the feature doesn't work end-to-end
  2. Add integration test - Verify cf prd generate --template lean actually uses the lean template

Should Fix (Important)

  1. 🔧 Add better error handling in load_template_from_file
  2. 🔧 Validate file paths in import/export commands
  3. 🔧 Add test for malformed YAML handling

Nice to Have (Enhancement)

  1. 💡 Consider Jinja2 sandboxing for untrusted templates
  2. 💡 Log when custom templates override built-ins
  3. 💡 Add template authoring documentation

Verdict

REQUEST CHANGES - The implementation is solid, but the critical integration gap prevents the feature from working. Once the template is actually passed to generate_prd() and tested end-to-end, this will be ready to merge.

The architecture is excellent and the groundwork is well done. The fix should be straightforward - just needs to connect the last pieces together.

Great work on the template system design! 🎉

The --template flag was validated but never passed to generate_prd().

Changes:
- Add template_id parameter to PrdDiscoverySession.generate_prd()
- Add _build_prd_prompt() to construct template-aware prompts
- Pass template_id from CLI to session.generate_prd()
- Store template_id in PRD metadata for traceability
- Add tests for template integration

The LLM prompt is now dynamically constructed based on the selected
template's sections, ensuring the generated PRD follows the chosen format.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@codeframe/planning/prd_templates.py`:
- Around line 753-758: The project template load path is skipped when
workspace_path is None, so project templates in the current repo aren't loaded;
change the logic that calls get_project_template_dir to default to the current
working directory when workspace_path is omitted. Specifically, update the
PrdTemplateManager code that calls
self._load_from_directory(get_project_template_dir(workspace_path)) to compute
project_dir = workspace_path or Path.cwd() (or equivalent) and then call
self._load_from_directory(get_project_template_dir(project_dir)) so
get_project_template_dir and _load_from_directory still get used but with a CWD
fallback.
- Around line 860-875: import_template currently only loads into memory; update
it to validate the loaded template and persist it to the templates directory so
it survives process restarts. After calling load_template_from_file(source_path)
in import_template, run the template validation routine (e.g.,
template.validate() or a shared validate_template(template) helper) and
raise/log on validation errors; then write the template YAML/json to the
persistent templates directory (use the existing templates storage helper or
write to self.templates_dir with a filename based on template.id), update
self.templates[template.id] only after successful validation and file write, and
keep the logger.info call to reflect the persisted import.

Comment on lines +753 to +758
# Load custom templates from global directory
self._load_from_directory(get_global_template_dir())

# Load project templates (override global)
if workspace_path:
self._load_from_directory(get_project_template_dir(workspace_path))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Project templates are skipped when workspace_path is omitted.
CLI commands construct PrdTemplateManager() without a workspace path, so .codeframe/templates/prd in the current project is never loaded. That makes project-level templates undiscoverable and breaks --template for local custom templates.

💡 Suggested fix (load project templates from CWD by default)
-        # Load project templates (override global)
-        if workspace_path:
-            self._load_from_directory(get_project_template_dir(workspace_path))
+        # Load project templates (override global)
+        project_dir = (
+            get_project_template_dir(workspace_path)
+            if workspace_path
+            else get_project_template_dir()
+        )
+        self._load_from_directory(project_dir)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Load custom templates from global directory
self._load_from_directory(get_global_template_dir())
# Load project templates (override global)
if workspace_path:
self._load_from_directory(get_project_template_dir(workspace_path))
# Load custom templates from global directory
self._load_from_directory(get_global_template_dir())
# Load project templates (override global)
project_dir = (
get_project_template_dir(workspace_path)
if workspace_path
else get_project_template_dir()
)
self._load_from_directory(project_dir)
🤖 Prompt for AI Agents
In `@codeframe/planning/prd_templates.py` around lines 753 - 758, The project
template load path is skipped when workspace_path is None, so project templates
in the current repo aren't loaded; change the logic that calls
get_project_template_dir to default to the current working directory when
workspace_path is omitted. Specifically, update the PrdTemplateManager code that
calls self._load_from_directory(get_project_template_dir(workspace_path)) to
compute project_dir = workspace_path or Path.cwd() (or equivalent) and then call
self._load_from_directory(get_project_template_dir(project_dir)) so
get_project_template_dir and _load_from_directory still get used but with a CWD
fallback.

Comment on lines 860 to 875
def import_template(self, source_path: Path) -> PrdTemplate:
"""Import a template from a file.

Args:
source_path: Path to YAML template file

Returns:
Imported template

Raises:
FileNotFoundError: If source file doesn't exist
"""
template = load_template_from_file(source_path)
self.templates[template.id] = template
logger.info(f"Imported template: {template.id}")
return template
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

import_template doesn’t persist or validate templates.
Right now the import only adds the template to the in-memory registry, so cf prd templates import reports success but the template isn’t available in later commands. Invalid templates can also be imported without detection.

🛠️ Suggested fix (validate + persist to template directory)
     def import_template(self, source_path: Path) -> PrdTemplate:
         """Import a template from a file.
@@
         Raises:
             FileNotFoundError: If source file doesn't exist
         """
         template = load_template_from_file(source_path)
+        errors = self.validate_template(template)
+        if errors:
+            raise ValueError("Invalid template: " + "; ".join(errors))
+
+        target_dir = (
+            get_project_template_dir(self.workspace_path)
+            if self.workspace_path
+            else get_project_template_dir()
+        )
+        target_path = target_dir / f"{template.id}.yaml"
+        save_template_to_file(template, target_path)
+
         self.templates[template.id] = template
         logger.info(f"Imported template: {template.id}")
         return template
🤖 Prompt for AI Agents
In `@codeframe/planning/prd_templates.py` around lines 860 - 875, import_template
currently only loads into memory; update it to validate the loaded template and
persist it to the templates directory so it survives process restarts. After
calling load_template_from_file(source_path) in import_template, run the
template validation routine (e.g., template.validate() or a shared
validate_template(template) helper) and raise/log on validation errors; then
write the template YAML/json to the persistent templates directory (use the
existing templates storage helper or write to self.templates_dir with a filename
based on template.id), update self.templates[template.id] only after successful
validation and file write, and keep the logger.info call to reflect the
persisted import.

@claude
Copy link

claude bot commented Jan 31, 2026

Updated Code Review: PRD Template System (#316)

I've reviewed the updates since my last review. Excellent work addressing the critical integration issue! The template is now properly wired into PRD generation. Here's my updated assessment:


✅ Critical Issue RESOLVED

The template integration is now working correctly:

# codeframe/cli/app.py:1433
prd_record = session.generate_prd(template_id=template)

# codeframe/core/prd_discovery.py:592-650
def generate_prd(self, template_id: Optional[str] = None) -> prd.PrdRecord:
    # Build prompt based on template
    prompt = self._build_prd_prompt(qa_history, template_id)
    ...
    # Store PRD with template_id in metadata
    record = prd.store(
        self.workspace,
        content=content,
        title=title,
        metadata={
            "template_id": template_id or "standard",
            ...
        },
    )

✅ Template parameter is passed to generate_prd()
✅ Template drives prompt construction via _build_prd_prompt()
✅ Template ID is stored in PRD metadata
✅ Signature test verifies the integration (test_generate_prd_stores_template_id_in_metadata)


🎯 Remaining Issues

1. Missing Error Handling in load_template_from_file (lines 709-726)

Issue: The code directly accesses dictionary keys without validation:

sections = [
    PrdTemplateSection(
        id=s["id"],          # KeyError if 'id' missing
        title=s["title"],    # KeyError if 'title' missing
        ...
    )
    for s in data.get("sections", [])
]

Impact: Malformed YAML files will crash with unhelpful KeyError instead of validation errors.

Recommendation:

try:
    sections = []
    for s in data.get("sections", []):
        try:
            section = PrdTemplateSection(
                id=s.get("id", ""),
                title=s.get("title", ""),
                source=s.get("source", ""),
                format_template=s.get("format_template", ""),
                required=s.get("required", True),
            )
            sections.append(section)
        except (KeyError, TypeError) as e:
            raise ValueError(f"Invalid section in template: {e}")
    
    template = PrdTemplate(
        id=data.get("id", ""),
        name=data.get("name", ""),
        version=data.get("version", 1),
        description=data.get("description", ""),
        sections=sections,
    )
    
    # Validate after loading
    manager = PrdTemplateManager()
    errors = manager.validate_template(template)
    if errors:
        raise ValueError(f"Invalid template: {', '.join(errors)}")
    
    return template
except (KeyError, TypeError) as e:
    raise ValueError(f"Malformed template file: {e}")

2. No Integration Test for End-to-End Template Usage

Missing test: Verify that cf prd generate --template lean actually produces a PRD using the lean template format.

Current coverage:

  • ✅ Template data structures
  • ✅ Manager methods
  • ✅ CLI command parsing
  • ✅ Signature verification (template_id parameter exists)
  • End-to-end workflow (template selection → PRD generation → output format)

Recommendation: Add a test like:

def test_generate_prd_with_lean_template_integration(workspace):
    """Verify lean template produces correct PRD structure."""
    from codeframe.core.prd_discovery import PrdDiscoverySession
    from codeframe.planning.prd_templates import PrdTemplateManager
    
    # Create session with mock LLM
    session = PrdDiscoverySession(workspace, api_key="test")
    # ... simulate discovery ...
    
    # Generate with lean template
    prd_record = session.generate_prd(template_id="lean")
    
    # Verify template was used
    assert prd_record.metadata["template_id"] == "lean"
    
    # Verify PRD structure matches lean template sections
    manager = PrdTemplateManager()
    lean = manager.get_template("lean")
    for section in lean.sections:
        assert section.title in prd_record.content

💡 Minor Suggestions (Non-blocking)

3. Path Validation in Import Command

The import command should validate file paths more robustly:

# codeframe/cli/app.py:706-716
try:
    if not source_path.exists():
        console.print(f"[red]Error:[/red] File not found: {source_path}")
        raise typer.Exit(1)
    if not source_path.is_file():
        console.print(f"[red]Error:[/red] Not a file: {source_path}")
        raise typer.Exit(1)
    template = manager.import_template(source_path)
except (PermissionError, OSError) as e:
    console.print(f"[red]Error:[/red] Cannot read file: {e}")
    raise typer.Exit(1)

4. Template Override Warning

When custom templates override built-ins, log a warning:

# In PrdTemplateManager.__init__
if template.id in self.templates:
    logger.info(f"Custom template '{template.id}' overriding built-in template")
self.templates[template.id] = template

5. Consider Jinja2 Sandboxing

For defense-in-depth against malicious templates:

from jinja2.sandbox import SandboxedEnvironment

self._env = SandboxedEnvironment(loader=BaseLoader())

Risk is low (users must explicitly import templates), but this adds an extra layer of safety.


📊 Summary

Must Fix Before Merge

  1. ⚠️ Add error handling in load_template_from_file - prevents cryptic KeyErrors
  2. ⚠️ Add end-to-end integration test - verifies templates actually work in practice

Should Consider (Nice to Have)

  1. 💡 Add file path validation in import command
  2. 💡 Log when custom templates override built-ins
  3. 💡 Consider Jinja2 sandboxing for untrusted templates

Verdict

APPROVE with minor suggestions - The critical integration issue has been resolved and the feature is now functional. The remaining issues are quality improvements that would make the code more robust but don't block the core functionality.

The architecture is solid, the implementation is clean, and the template system provides real value. Once the error handling is improved and an integration test is added, this will be production-ready.

Great work on the quick fix! 🎉

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@codeframe/cli/app.py`:
- Around line 685-712: prd_templates_import currently imports the template only
in memory via PrdTemplateManager.import_template, so the template disappears
after the process exits; after calling manager.import_template(source_path)
persist the template to the templates store (e.g., call a persistence method on
PrdTemplateManager such as manager.save_template(template) or implement
manager.persist_template(template) if missing) so the template YAML/metadata is
written into the global or project template directory; ensure the persistence
uses the same template.id/name and raises/handles errors similarly to the import
step so the CLI prints the success or error messages consistently.
- Around line 607-610: The CLI is instantiating PrdTemplateManager without a
workspace_path so it only loads built‑in/global templates; update the
PrdTemplateManager() calls (the ones followed by manager.list_templates()) to
pass the current project workspace path (e.g. Path.cwd() or your CLI's workspace
root variable) as the workspace_path argument so .codeframe/templates in the
repo is included in template resolution; make the same change for both
occurrences around the PrdTemplateManager instantiations.

In `@codeframe/core/prd_discovery.py`:
- Around line 662-670: PrdTemplateManager is being instantiated without the
workspace scope so project templates in .codeframe/templates aren’t loaded;
update the instantiation of PrdTemplateManager to pass the repository/workspace
path (e.g., PrdTemplateManager(workspace_path=workspace_path) or the appropriate
repo path variable available in this module) before calling
get_template(template_id), so project-scoped templates are considered when
building the prompt (still keep the existing fallback to PRD_GENERATION_PROMPT
when template_id is empty or get_template returns None).

Fixes three issues with template resolution and persistence:

1. CLI prd_templates_import now persists templates to disk
   - Added persist_template() method to PrdTemplateManager
   - Import with persist=True saves to .codeframe/templates/prd/
   - Shows save location in CLI output

2. CLI template commands now include project templates
   - Pass workspace_path=Path.cwd() to PrdTemplateManager
   - Project templates in .codeframe/templates/prd/ are discovered

3. prd_discovery._build_prd_prompt uses workspace scope
   - Passes self.workspace.path to PrdTemplateManager
   - Project templates can be used in PRD generation

Added 4 new tests for persist functionality.
@claude
Copy link

claude bot commented Jan 31, 2026

PR Review: PRD Template System Implementation

This is a solid implementation of the PRD template system. The code is well-structured, thoroughly tested, and follows the v2 architecture guidelines. Here's my detailed review:

✅ Strengths

1. Architecture Alignment

  • Correctly placed in codeframe/planning/ (headless core module)
  • No FastAPI dependencies - properly follows v2 CLI-first architecture
  • Clean separation: data structures → manager → CLI commands

2. Code Quality

  • Comprehensive docstrings on all public methods
  • Good use of dataclasses for PrdTemplateSection and PrdTemplate
  • Type hints throughout (Optional[Path], list[str], etc.)
  • Proper error handling with specific exceptions
  • Logging at appropriate levels

3. Security

  • Uses yaml.safe_load() instead of yaml.load()
  • Uses Jinja2's safe environment (no autoescape needed for markdown)
  • Path operations use Path objects consistently
  • File operations have proper error handling

4. Test Coverage

  • 32 unit tests for core template system
  • 12 CLI integration tests
  • Tests cover happy paths, edge cases, and error conditions
  • Good test organization with separate test classes

5. User Experience

  • 5 well-designed built-in templates covering different use cases
  • Clear CLI command structure (cf prd templates list/show/import/export)
  • Helpful error messages with actionable guidance

🔧 Issues to Address

1. Missing workspace_path in prd_generate command (line 1296)

The prd_generate command instantiates PrdTemplateManager() without workspace_path, which means it won't load project-specific templates from .codeframe/templates/prd/:

# Current (line 1296):
template_manager = PrdTemplateManager()

# Should be:
template_manager = PrdTemplateManager(workspace_path=workspace_path)

This is inconsistent with the other template commands (list, show, export, import) which all pass workspace_path=Path.cwd().

Impact: Users can't use project-specific templates with cf prd generate --template.


2. Template Validation Missing in load_template_from_file

The load_template_from_file function (line 693) doesn't validate template structure:

# Missing validation after loading:
def load_template_from_file(path: Path) -> PrdTemplate:
    with open(path, encoding="utf-8") as f:
        data = yaml.safe_load(f)
    
    # No validation of required fields or structure
    sections = [...]
    return PrdTemplate(...)

Potential issues:

  • Missing required fields (id, name, sections) cause KeyError
  • Invalid section structure causes unhelpful errors
  • Malformed YAML may create invalid templates

Recommendation: Add validation after loading:

  • Check required fields exist
  • Validate section structure
  • Raise ValueError with clear message on invalid templates

3. Jinja2 Template Injection Risk (Low Severity)

While yaml.safe_load() prevents YAML injection, user-provided Jinja2 templates in format_template could execute arbitrary Python if they contain expressions like {{ ''.__class__.__mro__[1].__subclasses__() }}.

Current risk: Low - only affects users who import untrusted templates
Recommendation: Consider using Jinja2's SandboxedEnvironment instead of Environment (line 761) for defense in depth:

from jinja2 import SandboxedEnvironment
self._env = SandboxedEnvironment(loader=BaseLoader())

💡 Suggestions (Non-Blocking)

1. Template Validation Method

Consider adding a validate() method to PrdTemplate:

def validate(self) -> None:
    """Validate template structure."""
    if not self.id or not self.name:
        raise ValueError("Template must have id and name")
    if not self.sections:
        raise ValueError("Template must have at least one section")
    for section in self.sections:
        if not section.id or not section.source:
            raise ValueError(f"Invalid section: {section}")

This would make validation explicit and reusable.


2. Template Version Compatibility

The version field exists but isn't used. Consider adding version compatibility checking if templates evolve over time.


3. Better Error Messages in render_template

Line 854 catches Exception broadly. Consider catching specific exceptions:

except TemplateSyntaxError as e:
    logger.warning(f"Template syntax error in {section.id}: {e}")
except UndefinedError as e:
    logger.warning(f"Missing variable in {section.id}: {e}")

📝 Minor Issues

  1. Line 757-758: The comment says "override global" but it's just adding to the dict (not actually overriding since dict keys are unique). This works correctly but the comment could be clearer.

  2. Line 906: get_global_template_dir() is called but the function implementation wasn't shown in the diff - verify it exists and creates the directory properly.

  3. Consistency: prd_templates_import uses persist=True (line 713) but could also use to_project=True explicitly for clarity.


🧪 Testing Recommendations

The existing tests are comprehensive, but consider adding:

  1. Security test: Import a template with Jinja2 injection attempt
  2. Edge case: Template with empty sections list
  3. Integration test: Full workflow of import → generate → verify output
  4. Error path: Invalid YAML structure in imported template

📊 Summary

Category Rating Notes
Architecture ✅ Excellent Follows v2 guidelines perfectly
Code Quality ✅ Excellent Clean, well-documented, typed
Security ⚠️ Good Minor Jinja2 sandboxing concern
Test Coverage ✅ Excellent 44 tests, good coverage
Bug Risk ⚠️ Medium workspace_path bug blocks key feature

Recommendation: Request changes for issue #1 (workspace_path), then approve. Issues #2-3 are nice-to-haves that can be addressed in follow-up PRs.


🎯 Action Items

Must fix before merge:

  1. Add workspace_path to PrdTemplateManager() in prd_generate command

Should fix (high priority):
2. Add template validation in load_template_from_file

Nice to have (lower priority):
3. Use SandboxedEnvironment for Jinja2
4. Add template version compatibility checking
5. Add security/edge-case tests


Great work overall! This feature is well-implemented and will provide significant value. The issues identified are fixable and mostly edge cases. Looking forward to the next iteration! 🚀

@claude
Copy link

claude bot commented Jan 31, 2026

Code Review: PRD Template System

I've reviewed PR #321 implementing the PRD template system. Overall, this is excellent work with solid architecture, comprehensive testing, and good adherence to the project's v2 principles. Here's my detailed feedback:


✅ Strengths

1. Architecture & Design

  • Clean separation of concerns: Template data structures, rendering logic, and CLI commands are well-separated
  • Headless core: Template system lives in codeframe/planning/ without FastAPI dependencies ✓
  • Follows v2 principles: CLI-first approach, no server dependencies
  • Good use of dataclasses: PrdTemplateSection and PrdTemplate are simple and clear
  • Proper abstraction: Jinja2 environment setup is encapsulated in PrdTemplateManager

2. Template System Features

  • 5 built-in templates cover diverse use cases (standard, lean, enterprise, user-story-map, technical-spec)
  • Flexible storage: Global (~/.codeframe) and project-level (.codeframe) template directories
  • Import/export workflow: Users can customize templates externally and re-import
  • Validation: Template structure and Jinja2 syntax validation before use
  • Custom filters: bullet_list, numbered_list, table, format_constraints provide useful formatting

3. CLI Integration

  • Early validation in prd_generate: Template is validated before starting discovery session (lines 1295-1303)
  • Good UX: Clear error messages, lists available templates on failure
  • Proper subcommand structure: cf prd templates list/show/import/export
  • Helpful examples in docstrings

4. Testing

  • 32 unit tests for core template functionality
  • 12 CLI tests for commands
  • Good coverage: Data structures, validation, rendering, import/export, CLI commands
  • Marked with pytest.mark.v2 for proper test organization

🔍 Issues & Recommendations

Critical: Template Validation Not Used During Import

Location: codeframe/cli/app.py:711-713

# Import and persist to project directory
template = manager.import_template(source_path, persist=True)

Issue: When importing a template, the code doesn't validate it before persisting. A user could import a broken template with invalid Jinja2 syntax, and it would be saved to disk. Later attempts to use it would fail.

Recommendation:

template = manager.import_template(source_path, persist=False)
errors = manager.validate_template(template)
if errors:
    console.print(f"[red]Error:[/red] Template validation failed:")
    for error in errors:
        console.print(f"  - {error}")
    raise typer.Exit(1)
    
# Now persist after validation
manager.persist_template(template, to_project=True)

Medium: Inconsistent Error Handling in Template Loading

Location: codeframe/planning/prd_templates.py:767-782

def _load_from_directory(self, directory: Path) -> None:
    # ...
    for path in directory.glob("*.yaml"):
        try:
            template = load_template_from_file(path)
            self.templates[template.id] = template
            logger.debug(f"Loaded template from {path}")
        except Exception as e:
            logger.warning(f"Failed to load template from {path}: {e}")

Issue: If a custom template has duplicate ID with a built-in, it silently overwrites without warning the user. This could be confusing.

Recommendation: Add a check and warning:

if template.id in self.templates:
    logger.warning(f"Template '{template.id}' already exists, overriding with {path}")

Minor: Template Not Actually Rendered

Location: codeframe/core/prd_discovery.py:652-706

The _build_prd_prompt method builds a prompt that tells the LLM about the template structure, but it doesn't actually use PrdTemplateManager.render_template(). The LLM generates markdown, but it's not guaranteed to follow the exact template structure.

Current behavior: LLM is instructed to follow template format
Expected behavior (based on PR description): Template is actually rendered with discovery data

Recommendation: Either:

  1. Post-process LLM output: Parse the generated PRD and run it through manager.render_template()
  2. Update PR description: Clarify that templates guide LLM generation rather than enforce structure
  3. Future enhancement: Add a post-generation rendering step that maps LLM output to template sections

This isn't blocking, but the current implementation is more of a "template-guided generation" rather than "template-based rendering."


Minor: Missing Template Validation in Tests

Location: tests/planning/test_prd_templates.py

Tests validate that individual templates pass validation, but don't test the prd generate --template integration end-to-end. Consider adding a test that:

  1. Runs cf prd generate --template lean (mocked LLM)
  2. Verifies the generated PRD includes lean template sections
  3. Verifies the metadata stores template_id: lean

Code Quality: Minor

  1. Unused import in prd_templates.py:16:

    from jinja2 import Environment, BaseLoader, TemplateSyntaxError

    BaseLoader is used, so this is fine. But consider adding a comment explaining why BaseLoader is used (loading templates from strings rather than files).

  2. Magic number in summarize function:

    def summarize(text: str, max_words: int = 50) -> str:

    Consider extracting to a constant: DEFAULT_SUMMARY_WORDS = 50

  3. Type hints: All type hints are present and correct ✓


🛡️ Security

  • No security issues identified
  • Template files are YAML (safe_load used) ✓
  • Jinja2 templates are sandboxed (no autoescape needed for markdown) ✓
  • No arbitrary code execution risks

📊 Performance

  • Template loading: Happens once at initialization, acceptable ✓
  • Jinja2 rendering: Fast for markdown generation ✓
  • File I/O: Minimal (only on import/export) ✓

No performance concerns.


📝 Documentation

  • Docstrings: Present and helpful ✓
  • CLI help text: Clear with examples ✓
  • PR description: Excellent with table of templates and examples ✓

Suggestion: Add a section to docs/GOLDEN_PATH.md or docs/CLI_WIREFRAME.md documenting the template system and custom template creation workflow.


✅ Approval Status

Recommendation: Approve with minor fixes

This PR is ready to merge after addressing the Critical issue (template validation on import). The other issues are nice-to-haves that can be addressed in follow-up PRs.

Priority Fixes Before Merge:

  1. ✅ Add validation before persisting imported templates

Optional Follow-ups (future PRs):

  1. Add duplicate template ID warning
  2. Clarify template rendering vs. LLM-guided generation
  3. Add end-to-end integration test
  4. Add template documentation to docs/

Great work on this feature! The template system is well-designed and will provide real value to users. 🎉

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
codeframe/core/prd_discovery.py (1)

592-650: ⚠️ Potential issue | 🟡 Minor

Align default template handling across prompt and metadata.

generate_prd() documents a default "standard" template, but _build_prd_prompt() bypasses templates when template_id is None while metadata/logging always record "standard". Resolve the default once and use it consistently.

🔧 Suggested fix
-        # Build prompt based on template
-        prompt = self._build_prd_prompt(qa_history, template_id)
+        resolved_template_id = template_id or "standard"
+        # Build prompt based on template
+        prompt = self._build_prd_prompt(qa_history, resolved_template_id)
@@
-                "template_id": template_id or "standard",
+                "template_id": resolved_template_id,
@@
-        logger.info(f"Generated PRD {record.id} from session {self.session_id} using template '{template_id or 'standard'}'")
+        logger.info(
+            f"Generated PRD {record.id} from session {self.session_id} using template '{resolved_template_id}'"
+        )
🤖 Fix all issues with AI agents
In `@tests/planning/test_prd_templates.py`:
- Around line 505-534: The test test_persist_template_to_project uses a string
containment check against saved_path which is OS-dependent; change the assertion
to compare Path objects or use saved_path.as_posix() to make it OS-agnostic.
Specifically, update the assertion after calling
PrdTemplateManager.persist_template (saved_path variable) to compare saved_path
to an expected Path built with pathlib (e.g., expected = workspace_path /
".codeframe" / "templates" / "prd" / "persisted-test.yaml") and assert
saved_path == expected or assert saved_path.exists() and saved_path ==
expected.resolve(), ensuring use of saved_path and persist_template identifiers
from the test.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
codeframe/core/prd_discovery.py (1)

615-642: ⚠️ Potential issue | 🟠 Major

Resolve and persist the actual template used (fallbacks are mislabeled).

When template_id is missing or invalid, _build_prd_prompt falls back to PRD_GENERATION_PROMPT, but metadata/logging still record "standard" or the invalid ID. That mislabels the stored PRD and can break downstream template lookups/exports. Please resolve the template first and persist the resolved ID (optionally also the requested one).

🛠️ Proposed fix to track requested vs resolved template IDs
-        # Build prompt based on template
-        prompt = self._build_prd_prompt(qa_history, template_id)
+        # Build prompt based on template
+        requested_template_id = template_id or "standard"
+        prompt, resolved_template_id = self._build_prd_prompt(qa_history, requested_template_id)

@@
-                "template_id": template_id or "standard",
+                "template_id": resolved_template_id,
+                "requested_template_id": requested_template_id,
@@
-        logger.info(f"Generated PRD {record.id} from session {self.session_id} using template '{template_id or 'standard'}'")
+        logger.info(
+            f"Generated PRD {record.id} from session {self.session_id} "
+            f"using template '{resolved_template_id or 'default'}'"
+        )

@@
-    def _build_prd_prompt(self, qa_history: str, template_id: Optional[str] = None) -> str:
+    def _build_prd_prompt(
+        self, qa_history: str, template_id: Optional[str] = None
+    ) -> tuple[str, Optional[str]]:

@@
-        if not template_id:
-            return PRD_GENERATION_PROMPT.format(qa_history=qa_history)
+        if not template_id:
+            return PRD_GENERATION_PROMPT.format(qa_history=qa_history), None

@@
-        if template is None:
+        if template is None:
             logger.warning(f"Template '{template_id}' not found, using default prompt")
-            return PRD_GENERATION_PROMPT.format(qa_history=qa_history)
+            return PRD_GENERATION_PROMPT.format(qa_history=qa_history), None

@@
-        return prompt
+        return prompt, template_id

Also applies to: 649-706

🧹 Nitpick comments (1)
codeframe/core/prd_discovery.py (1)

678-683: Include section format hints to honor template structure.

section.format is currently ignored, so templates can’t reliably steer lists/tables in the generated output. Consider surfacing this as a format hint in the prompt.

💡 Optional tweak to include format hints
-            required_note = " (required)" if section.required else " (optional)"
-            sections_spec.append(f"## {section.title}{required_note}\n{section.source} - related content")
+            required_note = " (required)" if section.required else " (optional)"
+            format_note = f" (format: {section.format})" if section.format else ""
+            sections_spec.append(
+                f"## {section.title}{required_note}{format_note}\n"
+                f"{section.source} - related content"
+            )

Test User added 2 commits January 30, 2026 20:36
When template_id is missing or invalid, _build_prd_prompt falls back to
the default prompt. Previously, metadata would incorrectly record the
original requested template ID.

Changes:
- _build_prd_prompt now returns tuple of (prompt, resolved_template_id)
- Resolved ID is "default" when no template used or fallback occurred
- Metadata stores resolved template_id for accurate tracking
- If requested template differs from resolved, store requested_template_id
- Logging now reflects actual template used
- Update generate_prd docstring to match actual fallback behavior
  (uses "default" not "standard" when template not found)
- Replace string path assertion with Path object comparison for
  cross-platform compatibility in test_persist_template_to_project
@claude
Copy link

claude bot commented Jan 31, 2026

Code Review - PRD Template System

Excellent implementation overall! The PRD template system is well-architected, thoroughly tested, and aligns well with v2 architecture principles.

✅ Strengths

  1. Clean Architecture - Core logic in codeframe/planning/ (headless, no FastAPI imports), follows v2 core-first principle perfectly
  2. Security - Uses yaml.safe_load() for YAML parsing, Jinja2 template validation before rendering
  3. Test Coverage - 32 unit tests for core + 12 CLI tests, covers edge cases, validation, rendering, import/export
  4. Design Quality - Dataclasses for clean models, template inheritance (built-in to global to project), custom Jinja2 filters
  5. User Experience - 5 thoughtful built-in templates, clear CLI commands, human-readable YAML format

Issues & Recommendations

1. Jinja2 Autoescape Not Enabled (Security - Medium Priority)
Location: codeframe/planning/prd_templates.py:761
While PRDs are markdown, enable autoescape for defense-in-depth to prevent potential XSS if PRDs are ever rendered to HTML.

2. Template Rendering Error Handling Too Broad (Code Quality - Low)
Location: codeframe/planning/prd_templates.py:853-855
Catching bare Exception is too broad. Consider catching specific exceptions like TemplateSyntaxError, UndefinedError, TypeError.

3. Template ID Validation (Enhancement - Optional)
Currently validates that template.id is not empty, but does not check for valid characters. Consider adding regex validation for filesystem safety.

4. Documentation - Template Source Field (Documentation - Low)
The source field (problem, users, features, constraints, tech_stack) is critical but not well documented in the docstring.

Alignment with v2 Architecture

Perfect adherence to v2 principles:

  • Core-first (no FastAPI imports)
  • CLI-first (works without server)
  • Headless domain logic
  • Optional adapters pattern (Jinja2 as template rendering adapter)

Final Verdict

Approve with minor suggestions. The security and code quality issues are low-severity and can be addressed in a follow-up PR. The core implementation is solid, well-tested, and ready to merge.

Recommendation: Merge this PR. Consider follow-up issues for:

  1. Jinja2 autoescape enablement
  2. Template ID format validation
  3. Enhanced error handling specificity

Great work!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
codeframe/core/prd_discovery.py (1)

592-655: ⚠️ Potential issue | 🟡 Minor

Align default template behavior with the docstring.

Docstring says the default is "standard", but _build_prd_prompt uses the legacy prompt and returns "default" when template_id is None. Please align the docstring with actual behavior (or pass "standard" by default if that’s the intended behavior).

📝 Suggested docstring fix (if legacy default is intended)
-            template_id: Optional PRD template ID to use for formatting.
-                        Defaults to "standard" if not provided.
+            template_id: Optional PRD template ID to use for formatting.
+                        If omitted, falls back to the legacy default prompt ("default").
🤖 Fix all issues with AI agents
In `@codeframe/core/prd_discovery.py`:
- Around line 657-715: The prompt builder _build_prd_prompt currently returns
the caller-supplied template_id and labels the section block "Required Sections"
even when optional sections are included; update it to return the resolved
template.id (use template.id when returning the tuple) and change the heading
logic for template.sections so the prompt either uses a neutral "Sections"
heading or splits into "Required Sections" and "Optional Sections" by iterating
template.sections and grouping by section.required; keep the rest of prompt
construction and return (prompt, template.id).

Comment on lines 657 to 715
def _build_prd_prompt(
self, qa_history: str, template_id: Optional[str] = None
) -> tuple[str, str]:
"""Build PRD generation prompt based on template.

Args:
qa_history: Formatted Q&A history string
template_id: Template ID to use (defaults to None, which uses default prompt)

Returns:
Tuple of (prompt string, resolved template ID)
The resolved template ID is "default" if no template was used,
or the actual template ID that was successfully loaded.
"""
from codeframe.planning.prd_templates import PrdTemplateManager
from pathlib import Path

# Use default prompt if no template specified
if not template_id:
return (PRD_GENERATION_PROMPT.format(qa_history=qa_history), "default")

# Pass workspace path to include project templates
workspace_path = Path(self.workspace.repo_path) if self.workspace.repo_path else None
manager = PrdTemplateManager(workspace_path=workspace_path)
template = manager.get_template(template_id)

if template is None:
logger.warning(f"Template '{template_id}' not found, falling back to default prompt")
return (PRD_GENERATION_PROMPT.format(qa_history=qa_history), "default")

# Build dynamic prompt from template sections
sections_spec = []
for section in template.sections:
required_note = " (required)" if section.required else " (optional)"
sections_spec.append(f"## {section.title}{required_note}\n{section.source} - related content")

sections_text = "\n\n".join(sections_spec)

prompt = f"""Generate a Product Requirements Document based on the discovery conversation.

## Discovery Conversation
{qa_history}

## Template: {template.name}
{template.description}

## Required Sections
Generate a markdown PRD with these sections in order:

# [Project Title - infer from conversation]

{sections_text}

---

Keep it concise but complete. Focus on actionable requirements.
Follow the template structure exactly. This PRD should be sufficient to generate development tasks."""

return (prompt, template_id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use the resolved template ID and clarify the section heading.

  • Return the actual template.id to avoid storing/logging a caller-supplied alias.
  • The heading says “Required Sections” but includes optional sections; consider renaming to “Sections” (or split required vs optional).
🔧 Suggested adjustment
-## Required Sections
+## Sections
@@
-        return (prompt, template_id)
+        return (prompt, template.id)
🤖 Prompt for AI Agents
In `@codeframe/core/prd_discovery.py` around lines 657 - 715, The prompt builder
_build_prd_prompt currently returns the caller-supplied template_id and labels
the section block "Required Sections" even when optional sections are included;
update it to return the resolved template.id (use template.id when returning the
tuple) and change the heading logic for template.sections so the prompt either
uses a neutral "Sections" heading or splits into "Required Sections" and
"Optional Sections" by iterating template.sections and grouping by
section.required; keep the rest of prompt construction and return (prompt,
template.id).

Ruff F401: Path import was redundant since tmp_path fixture
already provides a Path object for division operations.
@claude
Copy link

claude bot commented Jan 31, 2026

Code Review: PRD Template System

I've reviewed PR #321 implementing the PRD template system. This is a well-designed feature that aligns with the v2 architecture. Here's my feedback:


Strengths

Architecture & Design

  • Excellent v2 compliance: The implementation is completely headless (no FastAPI imports in core modules), following docs/GOLDEN_PATH.md correctly
  • Proper separation: Templates live in codeframe/planning/, maintain clean boundaries
  • Smart integration: The generate_prd() method gracefully falls back to default prompt when template not found, with proper logging
  • Good extensibility: Three-tier template system (built-in → global → project) provides excellent customization path

Code Quality

  • Safe YAML handling: Correctly uses yaml.safe_load() (line 707) - no security vulnerabilities
  • Comprehensive validation: validate_template() checks both structure and Jinja2 syntax before rendering
  • Error handling: Graceful degradation when template sections fail to render (line 854-855)
  • Type safety: Proper use of dataclasses with type hints throughout

Testing

  • Strong coverage: 44 tests (32 unit + 12 CLI) covering all major paths
  • Good test organization: Clear separation between unit tests and CLI integration tests
  • Proper v2 marking: Tests correctly marked with pytestmark = pytest.mark.v2

🔍 Issues & Recommendations

1. Template Function Security (Low Priority)

The Jinja2 custom filters in prd_template_functions.py are simple and safe, but consider:

  • Line 62-64: The table() function doesn't escape column values that could contain pipe characters |, potentially breaking markdown tables
  • Recommendation: Add basic sanitization:
row_values = [str(item.get(col, "")).replace("|", "\\|") for col in columns]

2. Template Validation Gap (Medium Priority)

validate_template() checks Jinja2 syntax but doesn't validate that referenced filters exist.

  • Example: A template using {{ users | nonexistent_filter }} would pass validation but fail at render time
  • Location: codeframe/planning/prd_templates.py:803-831
  • Recommendation: During validation, parse template AST to check filter names against TEMPLATE_FUNCTIONS

3. CLI UX Enhancement (Low Priority)

The --template flag in prd generate defaults to "standard" (line 1243-1247), which is good. However:

  • Suggestion: Add --list-templates flag to prd generate for discoverability without leaving the command context
  • Current flow requires: cf prd templates list → copy ID → cf prd generate --template <id>
  • Better flow: cf prd generate --list-templates shows inline, or interactive prompt when --template omitted

4. Documentation in Templates (Low Priority)

Built-in templates have hardcoded placeholder content (e.g., lines 304-307, 373-387). Consider:

  • Current: Static placeholder text in templates
  • Better: Document in template descriptions that these are "framework templates" requiring customization
  • Best: Add # CUSTOMIZE comments in exported YAML to guide users

5. Template Metadata (Enhancement)

The PrdTemplate model has version field but no migration/upgrade logic. For future-proofing:

  • Recommendation: Add schema_version to distinguish template format versions from template content versions
  • Use case: If you later add new features to templates, you can handle older templates gracefully

6. Error Message Clarity (Low Priority)

prd_discovery.py:685-686 logs warning but user doesn't see it in CLI unless verbose mode. Consider:

  • Current: Silent fallback to default template
  • Better: Print warning to console when template not found (especially if user explicitly specified it)

📋 Code Specific Observations

Good Patterns Observed

  1. Line 617: Proper tuple return from _build_prd_prompt() for both prompt and resolved template ID - clean design
  2. Line 640-642: Tracking both requested and resolved template IDs in metadata - excellent for debugging/analytics
  3. Line 753-758: Loading project templates after global templates allows proper override behavior
  4. Line 849-858: Template rendering error handling preserves section structure while showing errors

Minor Improvements

  1. prd_templates.py:762-763: Consider extracting filter registration to a method for testability:
def _setup_jinja_env(self) -> Environment:
    env = Environment(loader=BaseLoader())
    for name, func in TEMPLATE_FUNCTIONS.items():
        env.filters[name] = func
    return env
  1. prd_template_functions.py:69-88: The summarize() function is a good placeholder, but document that it's naive truncation (already done in docstring ✅)

🎯 Testing Recommendations

Additional Test Cases to Consider

  1. Template with invalid Jinja2 syntax: Test that validation catches malformed templates before render
  2. Template using non-existent filter: Test runtime behavior (currently falls back gracefully, should be documented)
  3. Template with empty sections list: Test validation error
  4. Concurrent template imports: Test thread safety if manager is shared across requests
  5. Very large templates: Test performance with 20+ sections

🔐 Security Assessment

Overall: SECURE

  • Uses yaml.safe_load() for YAML parsing (prevents code execution)
  • Jinja2 templates use BaseLoader (no file system access)
  • Custom filters are simple string operations (no eval/exec)
  • Template paths use proper Path objects (prevents path traversal)

One consideration: Users can create custom templates with arbitrary Jinja2 logic. While safe by default, document that template files should be treated like code (don't load untrusted templates).


📊 Performance Considerations

  • Template loading: Lazy loading only from existing directories ✅
  • Jinja2 compilation: Templates compiled on each render - consider caching if PRD generation becomes frequent
  • YAML parsing: Safe but not the fastest - acceptable for this use case

Final Verdict

LGTM with minor suggestions 🎉

This is a well-crafted feature that:

  • Follows v2 architecture principles
  • Has comprehensive test coverage
  • Provides clear user value
  • Maintains backward compatibility (default template = existing behavior)

The suggestions above are enhancements, not blockers. The code is production-ready as-is.

Recommended Next Steps

  1. ✅ Merge as-is
  2. 📝 Create follow-up issues for:

Great work on maintaining the high quality bar while adding meaningful functionality!


Review conducted following CLAUDE.md v2 principles - focusing on Golden Path alignment, headless architecture, and core-first design.

@claude
Copy link

claude bot commented Jan 31, 2026

Code Review - PR #321: PRD Template System

This is a well-designed and comprehensive implementation of a PRD template system. The code quality is excellent with strong architecture, thorough testing, and good integration with existing systems.

Strengths

Architecture & Design

  • Clean separation of concerns: Template data structures are distinct from template management
  • Jinja2 integration: Smart choice for a proven templating engine with custom filters for markdown generation
  • Multi-layer template loading: Builtin → Global → Project templates with proper override semantics
  • Fallback to default: Graceful degradation when templates aren't found (prd_discovery.py:684-686)

Code Quality

  • Strong type hints: All functions use proper type annotations
  • Comprehensive docstrings: Clear documentation with Args/Returns sections
  • Error handling: Template validation (prd_templates.py:803-833) prevents invalid templates from breaking the system
  • Logging: Appropriate use of debug/info/warning levels throughout

Testing

  • 44 new tests (32 unit + 12 CLI) with no regressions in existing 1541 tests
  • Well-structured test classes covering data structures, manager operations, and CLI
  • Edge cases covered: Empty data, missing templates, syntax errors in templates

CLI Design

  • Consistent command structure: cf prd templates follows existing patterns (cf templates for tasks)
  • User-friendly: Clear help text, error messages, and examples
  • Progressive disclosure: List → Show → Export/Import workflow

Observations & Recommendations

1. Template Rendering Error Handling (prd_templates.py:853-855)

Current behavior inserts error placeholders when sections fail to render. This is good for robustness but might hide issues during development.

Recommendation: Consider adding a strict_mode parameter to render_template() that raises on errors, or log at ERROR level instead of WARNING to help developers catch template bugs early.

2. Template Function Security

Since templates can be imported from arbitrary files, consider adding autoescape=True to the Jinja2 environment initialization for defense in depth.

3. Template Validation Timing (prd_templates.py:767-782)

_load_from_directory() logs warnings but continues on failure.

Enhancement: Add a validate parameter to import_template() to give users immediate feedback on broken templates instead of discovering issues at render time.

4. Integration with Discovery Session (prd_discovery.py:592-656)

Excellent integration with optional template_id parameter, metadata tracking, and dynamic prompt construction.

Note: Consider adding an example output in the prompt showing the expected format for LLM guidance.

Potential Issues

1. Template Validation in CLI

Verify that the CLI (app.py around line 1440) validates the template exists before starting the discovery session, with clear error messages listing available templates.

2. Template ID Collision Handling

When loading templates from multiple directories, later templates silently override earlier ones (prd_templates.py:779). This could cause confusion.

Suggestion: Add a warning when overriding existing templates.

Security

Generally secure, but note:

  1. File path injection: import_template() and export_template() accept user-provided paths. Consider validating paths or using Path.resolve() to prevent directory traversal.
  2. YAML deserialization: Ensure yaml.safe_load() is consistently used (appears to be the case).

Documentation

Strong documentation throughout with one gap:

Missing: Add a brief section to docs/CLI_WIREFRAME.md documenting the new prd templates subcommand tree.

Test Coverage

Excellent coverage with one gap: Template validation during import. Consider adding a test that verifies import rejects templates with syntax errors when validation is enabled.

Summary

This PR is ready to merge with minor enhancements recommended for future iterations:

Priority recommendations (before merge):

  1. Verify template validation exists in CLI
  2. Add warning when templates override builtins

Suggested enhancements (future PRs):

  1. Add strict_mode to render_template()
  2. Add validate parameter to import_template()
  3. Update docs/CLI_WIREFRAME.md
  4. Add path validation to import/export commands
  5. Add Jinja2 autoescape

Score: 9/10 - Excellent implementation with solid architecture and testing

Great work! This is a well-crafted feature that significantly improves PRD customization.

1. CLI template validation now includes project templates
   - Pass workspace_path to PrdTemplateManager during validation
   - Project templates in .codeframe/templates/prd/ are now discoverable

2. Template ID collision handling
   - Log warning when a template overrides an existing one
   - Helps users understand which template version is active

Added test for override warning behavior.
@claude
Copy link

claude bot commented Jan 31, 2026

Code Review - PRD Template System

This PR implements a well-designed PRD template system with Jinja2 rendering. The implementation is solid overall, with good test coverage and clean separation of concerns. Below are my findings:


Strengths

  1. Excellent Architecture Alignment

    • Follows v2 design principles: headless, core-first implementation
    • No FastAPI dependencies in core/ modules
    • Clean separation: templates in planning/, CLI in cli/app.py
    • Proper integration with existing discovery flow
  2. Comprehensive Built-in Templates

    • 5 well-designed templates covering diverse use cases
    • Good variety: standard, lean, enterprise, user-story-map, technical-spec
    • Templates use appropriate Jinja2 features (filters, conditionals, loops)
  3. Strong Test Coverage

    • 32 unit tests for core functionality
    • 12 CLI tests for template commands
    • Tests cover validation, rendering, persistence, edge cases
  4. Good User Experience

    • Intuitive CLI commands: cf prd templates list/show/import/export
    • Template selection via --template flag
    • Helpful error messages and fallback to default
  5. Security Practices

    • Uses yaml.safe_load() and yaml.safe_dump() (prevents arbitrary code execution)
    • Proper file path handling with Path objects
    • Template validation before rendering

⚠️ Issues & Concerns

1. Missing Jinja2 Autoescape (Security)

Severity: Medium-High

The Jinja2 environment is created without autoescape:

# codeframe/planning/prd_templates.py:761
self._env = Environment(loader=BaseLoader())

Risk: If discovery data contains user-controlled HTML/JavaScript, it could be injected into rendered PRDs.

Fix:

self._env = Environment(loader=BaseLoader(), autoescape=True)

While PRDs are markdown (not HTML), this is a defense-in-depth measure. If PRDs are ever rendered to HTML, unescaped content could cause XSS vulnerabilities.


2. Path Traversal Vulnerability

Severity: Medium

In load_template_from_file() and template import/export functions, there's no validation that file paths stay within expected directories:

# codeframe/planning/prd_templates.py:693-707
def load_template_from_file(path: Path) -> PrdTemplate:
    with open(path, encoding="utf-8") as f:  # No path validation
        data = yaml.safe_load(f)

Risk: An attacker could potentially read arbitrary YAML files:

cf prd templates import ../../../etc/passwd  # Hypothetical

Fix: Add path validation in load_template_from_file():

def load_template_from_file(path: Path) -> PrdTemplate:
    # Resolve to absolute path and check it's a file
    path = path.resolve()
    if not path.is_file():
        raise FileNotFoundError(f"Template file not found: {path}")
    
    # Optional: Check file extension
    if path.suffix not in [".yaml", ".yml"]:
        raise ValueError(f"Template must be a YAML file, got: {path.suffix}")
    
    with open(path, encoding="utf-8") as f:
        data = yaml.safe_load(f)
    # ... rest of function

3. Template Rendering Error Handling

Severity: Low

Template rendering failures produce generic error messages:

# codeframe/planning/prd_templates.py:859-861
except Exception as e:
    logger.warning(f"Failed to render section {section.id}: {e}")
    sections.append(f"## {section.title}\n\n*Error rendering section: {e}*")

Issue: This catches all exceptions (including KeyboardInterrupt, SystemExit). Also, users won't see the error unless they check logs.

Fix:

except TemplateSyntaxError as e:
    logger.error(f"Template syntax error in section {section.id}: {e}")
    sections.append(f"## {section.title}\n\n*Template syntax error: {e}*")
except Exception as e:
    logger.error(f"Failed to render section {section.id}: {e}", exc_info=True)
    sections.append(f"## {section.title}\n\n*Error rendering section. Check logs for details.*")

4. Missing Input Validation in Template Import

Severity: Low-Medium

The import_template() function doesn't validate template structure before registering:

# codeframe/planning/prd_templates.py:866-886
def import_template(self, source_path: Path, persist: bool = False) -> PrdTemplate:
    template = load_template_from_file(source_path)
    self.templates[template.id] = template  # No validation!

Issue: Malformed templates could be registered, causing errors later during rendering.

Fix:

def import_template(self, source_path: Path, persist: bool = False) -> PrdTemplate:
    template = load_template_from_file(source_path)
    
    # Validate before registering
    errors = self.validate_template(template)
    if errors:
        raise ValidationError(f"Invalid template: {', '.join(errors)}")
    
    self.templates[template.id] = template
    # ... rest of function

5. Workspace Path Handling in CLI

Severity: Low

In prd_generate(), workspace path defaults to Path.cwd(), but PrdTemplateManager expects it:

# codeframe/cli/app.py:1275-1279
workspace_path = repo_path if repo_path else Path.cwd()
workspace = Workspace.load(workspace_path)

# Then in prd_discovery.py:681
workspace_path = Path(self.workspace.repo_path) if self.workspace.repo_path else None
manager = PrdTemplateManager(workspace_path=workspace_path)

Issue: If workspace.repo_path is None, project templates won't be loaded even if we're in a valid workspace.

Fix: Ensure consistent workspace path handling or make workspace.repo_path always set during initialization.


📋 Minor Suggestions

  1. Type Hints for Discovery Data
    Consider adding a TypedDict for discovery_data structure:

    from typing import TypedDict
    
    class DiscoveryData(TypedDict, total=False):
        problem: str
        users: list[str]
        features: list[str]
        constraints: dict[str, Any]
        tech_stack: list[str]
  2. Template Validation in CLI
    The prd_generate() command validates template existence but doesn't show available templates on error:

    # Add helpful error message
    if template_obj is None:
        available = manager.list_templates()
        template_ids = [t.id for t in available]
        console.print(f"[red]Error:[/red] Template '{template}' not found.")
        console.print(f"Available templates: {', '.join(template_ids)}")
        raise typer.Exit(1)
  3. Documentation in Templates
    Consider adding a metadata field to templates for more context:

    @dataclass
    class PrdTemplate:
        # ... existing fields
        metadata: dict[str, Any] = field(default_factory=dict)  # author, tags, examples
  4. Template Function Safety
    The summarize() function truncates naively. Consider word boundaries:

    def summarize(text: str, max_words: int = 50) -> str:
        if not text:
            return ""
        words = text.split()
        if len(words) <= max_words:
            return text
        # Truncate at word boundary
        return " ".join(words[:max_words]).rstrip('.,;:') + "..."
  5. Logging Improvements
    Add more debug logging for troubleshooting:

    logger.debug(f"Rendering section '{section.id}' with data keys: {list(discovery_data.keys())}")

🧪 Test Coverage Gaps

The tests are comprehensive, but consider adding:

  1. Security tests: Test path traversal prevention, XSS in templates
  2. Error recovery tests: What happens when template directory is unreadable?
  3. Integration test: Full flow from cf prd generate --template lean to file creation
  4. Edge cases: Empty discovery data, very large templates, Unicode handling

📊 Performance Considerations

  1. Template Caching: Jinja2 templates are re-parsed on every render. Consider caching compiled templates:

    from functools import lru_cache
    
    @lru_cache(maxsize=128)
    def _compile_template(template_str: str):
        return self._env.from_string(template_str)
  2. Lazy Loading: All templates are loaded eagerly in __init__(). For large template libraries, consider lazy loading.


📝 Documentation

The code is well-documented with docstrings. Consider adding:

  • User guide: How to create custom templates
  • Example custom template YAML
  • Migration guide: Converting old PRDs to new template format

Summary

Recommendation: Approve with required changes

This is a well-implemented feature that adds significant value. The architecture is sound and follows CodeFRAME's v2 principles. However, please address the security concerns before merging:

Required changes:

  1. Add autoescape=True to Jinja2 environment
  2. Add path validation to prevent traversal attacks
  3. Validate templates before importing

Recommended changes:
4. Improve error handling in template rendering
5. Add validation error display in CLI
6. Add security-focused tests

Great work overall! This feature will significantly improve the flexibility of PRD generation. 🎉

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@codeframe/planning/prd_templates.py`:
- Around line 869-872: The docstring for import_template() claims the persist
parameter controls saving to project/global directories but the implementation
always calls persist_template(..., to_project=True); update import_template to
either accept a new parameter (e.g., to_project: bool or
target=('project'|'global')) and pass it through to persist_template, or change
the docstring to state it always persists to the project directory; adjust
references to persist_template and the persist parameter in import_template() to
reflect the chosen behavior (ensure the function signature, docstring, and the
call to persist_template are consistent).

1. Jinja2 autoescape (XSS prevention)
   - Enable autoescape=True in Jinja2 Environment
   - Prevents HTML injection if PRDs are rendered downstream

2. Path traversal prevention
   - Validate file exists and is a YAML file in load_template_from_file()
   - Reject non-.yaml/.yml files

3. Improved error handling in render_template()
   - Catch TemplateSyntaxError separately with specific message
   - Re-raise KeyboardInterrupt/SystemExit
   - Log full traceback for unexpected errors

4. Template validation on import
   - Validate template before registering in import_template()
   - Raise ValueError with validation errors

Added 4 security-focused tests covering all validations.
@claude
Copy link

claude bot commented Jan 31, 2026

Code Review Summary

I've reviewed this PRD template system implementation. The architecture is solid and aligns well with CodeFRAME's v2 headless, CLI-first principles. Here are my observations:

Strengths

  1. Clean separation of concerns: Template system in planning/, core logic remains headless, CLI properly wraps functionality
  2. Comprehensive test coverage: 40 unit tests + 14 CLI tests covering data structures, rendering, validation, and persistence
  3. Security-conscious: Jinja2 autoescape enabled (prd_templates.py:771) for defense-in-depth
  4. Good error handling: Validation, fallbacks, and informative error messages throughout
  5. Documentation: Clear docstrings and inline comments explaining design decisions

🔍 Observations & Suggestions

1. Template Resolution Transparency (Minor)

The current implementation tracks resolved templates correctly in metadata (prd_discovery.py:638-642), which is good for debugging. However, consider logging a warning when fallback occurs so users know their requested template wasn't found:

# prd_discovery.py:685
if template is None:
    logger.warning(f"Template '{template_id}' not found, falling back to default prompt")
    console.print(f"[yellow]Warning:[/yellow] Template '{template_id}' not found, using default")
    return (PRD_GENERATION_PROMPT.format(qa_history=qa_history), "default")

This would improve UX when users typo a template name.

2. Section Format Hints (Enhancement Opportunity)

As CodeRabbit noted, section.format is currently unused. The PrdTemplateSection dataclass has format_template but not a separate format field for hinting (list/table/paragraph). If this was intended, consider:

  • Either add a format: Optional[str] field to guide LLM output structure
  • Or remove any references to it to avoid confusion
  • The current Jinja2 filters (bullet_list, numbered_list, table) handle formatting well post-generation

3. Template Validation Timing

Good: Validation happens at import time (prd_templates.py:898-900) which prevents bad templates from entering the system. This is the right approach.

4. Jinja2 Environment Reuse (Performance - Very Minor)

The Jinja2 environment is created once per manager instance (prd_templates.py:771-773), which is good. Consider whether the BaseLoader could be replaced with a DictLoader or FunctionLoader for better error messages, but current implementation is fine for v2 Phase 1.

5. Test Path Assertions ⚠️

CodeRabbit correctly identified the OS-dependent path assertion issue in test_prd_templates.py:505-534. This should be fixed to prevent test failures on Windows:

# Use Path comparison instead of string containment
expected_path = workspace_path / ".codeframe" / "templates" / "prd" / "persisted-test.yaml"
assert saved_path == expected_path or saved_path.resolve() == expected_path.resolve()

🎯 Alignment with CLAUDE.md

Follows v2 Architecture Rules:

  • Core is headless (no FastAPI imports in planning modules)
  • CLI works without server (cf prd templates list is standalone)
  • Proper separation: planning/ for templates, cli/ for commands, core/ for discovery

Golden Path Compatible:

  • Enhances cf prd generate without breaking existing workflow
  • Template parameter is optional (defaults to 'standard')
  • Backward compatible with non-template PRD generation

📋 Blockers vs. Nice-to-Haves

Must fix before merge:

  • OS-agnostic path assertions in tests (per CodeRabbit's comment)

Recommended for this PR:

  • Add console warning when template fallback occurs (UX improvement)
  • Clarify section.format intent (either implement or document as future work)

Can defer to future PRs:

  • Enhanced template discovery (cf prd templates search)
  • Template validation CLI command (cf prd templates validate <file>)
  • LLM-powered summarize filter implementation (currently a stub)

🚀 Overall Assessment

This is high-quality work that adds valuable flexibility to PRD generation without compromising the v2 architecture. The template system is well-designed, tested, and documented. Once the path assertion fix is applied, this is ready to merge.

Recommendation: Approve with minor fixes.


Review based on: CodeFRAME v2 Strategic Roadmap, CLAUDE.md guidelines, and existing codebase patterns

Use .resolve() on both paths to handle Windows path normalization
differences (e.g., case sensitivity, symlink resolution).
@claude
Copy link

claude bot commented Jan 31, 2026

Code Review - PR #321: PRD Template System

This is a well-designed and comprehensive implementation of the PRD template system. The feature adds significant value by allowing users to customize PRD output formats.

Strengths

  1. Clean Architecture - Excellent separation of concerns, follows v2 architecture (headless core, CLI-first)
  2. Comprehensive Testing - 32 unit tests + 12 CLI tests with good edge case coverage
  3. User Experience - Intuitive CLI commands with helpful error messages
  4. Security - Jinja2 autoescape enabled, file validation, template validation before registration
  5. Documentation - Clear docstrings and test names

Issues to Address (from CodeRabbit reviews)

1. CLI Commands Missing workspace_path (Medium Priority)

Problem: CLI commands instantiate PrdTemplateManager() without workspace path, missing project-scoped templates.

Fix: Pass workspace path in CLI commands (app.py lines 607, 633, 668, 696, 1271):

manager = PrdTemplateManager(workspace_path=Path.cwd())

2. Project Template Directory Fallback (Medium Priority)

Problem: When workspace_path=None, project templates in .codeframe/templates/prd/ are not loaded.

Fix: Either default to cwd or require callers to pass workspace_path.

3. Template Resolution Consistency (Low Priority)

The code already correctly returns tuple from _build_prd_prompt(). Just ensure "default" template ID is documented as the legacy prompt format.

Suggestions for Improvement

  1. Security Enhancements - Add file size limits (1MB), template ID format validation (prevent path traversal), section count limits
  2. Prompt Clarity - Separate "Required Sections" and "Optional Sections" in prompt generation
  3. Import/Persist API - Consider adding to_project parameter for more control over where templates are saved

Alignment with CLAUDE.md Guidelines

  • Follows v2 architecture (headless core, CLI-first)
  • Proper module structure in codeframe/planning/
  • Comprehensive testing
  • Integrates with Golden Path (cf prd generate)

Summary

High-quality PR with good architecture, comprehensive tests, and security considerations.

Before merging:

  1. Pass workspace_path to PrdTemplateManager in CLI commands
  2. Consider workspace_path fallback behavior
  3. Optional: Add security enhancements (file size limits, ID validation)

Recommendation: APPROVE with minor fixes for workspace path handling

Great work! The template system is well-designed and will be very useful.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@tests/planning/test_prd_templates.py`:
- Around line 495-504: The test test_get_global_template_dir currently patches
only HOME which fails on Windows because Path.home() respects USERPROFILE there;
update the test to set both environment vars (setenv("HOME", ...) and
setenv("USERPROFILE", ...)) before calling get_global_template_dir so
Path.home() resolves correctly on all platforms and the assertions around
"templates" and "prd" continue to hold.

Code quality improvements based on CodeRabbit review:

1. Empty YAML file handling
   - Add null check after yaml.safe_load() with clear error message

2. list_templates sorting robustness
   - Handle name=None with fallback to empty string

3. import_template docstring accuracy
   - Fix to match behavior (always saves to project directory)

4. Support both .yaml and .yml extensions
   - _load_from_directory now globs both patterns

5. Cross-platform test compatibility
   - Set both HOME and USERPROFILE for Path.home() tests

6. Section heading clarity
   - Change "Required Sections" to "Sections" in prompt

Added test for empty YAML file rejection.
@claude
Copy link

claude bot commented Jan 31, 2026

Code Review - PR #321: PRD Template System

I've completed my review of this PR, comparing it against the previous reviews and the current state of the code. This is an excellent implementation that has addressed all the critical issues from earlier reviews.


Previously Identified Issues - ALL RESOLVED

1. Template Not Passed to PRD Generation (CRITICAL) ✅ FIXED

Previous Issue: Template was validated but never passed to generate_prd().

Current State: RESOLVED

  • app.py:1440: session.generate_prd(template_id=template)
  • prd_discovery.py:592-656: Proper template handling with fallback logic ✓
  • Metadata tracking records both requested and resolved template IDs ✓

2. Workspace Path Not Passed to Template Manager (MEDIUM) ✅ FIXED

Previous Issue: CLI commands instantiated PrdTemplateManager() without workspace path.

Current State: RESOLVED

  • app.py:610,637,675,1296: All CLI commands now pass workspace_path
  • prd_discovery.py:680: Discovery session also passes workspace path ✓
  • Project-scoped templates are now properly discovered ✓

3. Security Concerns ✅ ADDRESSED

Previous State: Jinja2 autoescape was already enabled.

Current State: VERIFIED SECURE

  • prd_templates.py:774: autoescape=True confirmed ✓
  • Template validation before import (prd_templates.py:903-905) ✓
  • Safe error handling in rendering (prd_templates.py:874-881) ✓

🔍 Architecture Review

Alignment with CLAUDE.md Guidelines ✅

v2 Architecture Compliance:

  • ✅ Core is headless: No FastAPI imports in planning/ modules
  • ✅ CLI-first: cf prd templates list works standalone
  • ✅ Proper separation: planning/ for templates, cli/ for commands, core/ for discovery
  • ✅ Golden Path compatible: Enhances existing workflow without breaking changes
  • ✅ Backward compatible: Template parameter optional, defaults to 'standard'

Module Structure:

codeframe/
├── planning/
│   ├── prd_templates.py          # Template models + manager (901 lines)
│   └── prd_template_functions.py # Jinja2 filters (137 lines)
├── core/
│   └── prd_discovery.py          # Template integration via _build_prd_prompt()
└── cli/
    └── app.py                    # CLI commands (list/show/export/import)

This is exactly the right architecture for v2.


💎 Code Quality Highlights

1. Robust Template Resolution (prd_discovery.py:658-716)

def _build_prd_prompt(self, qa_history: str, template_id: Optional[str] = None):
    # Graceful fallback to default prompt
    if not template_id:
        return (PRD_GENERATION_PROMPT.format(qa_history=qa_history), "default")
    
    # Proper workspace path handling
    workspace_path = Path(self.workspace.repo_path) if self.workspace.repo_path else None
    manager = PrdTemplateManager(workspace_path=workspace_path)
    
    # Fallback with logging
    if template is None:
        logger.warning(f"Template '{template_id}' not found, falling back to default")
        return (PRD_GENERATION_PROMPT.format(qa_history=qa_history), "default")

Excellent: Defensive programming with clear logging and fallback behavior.

2. Template Metadata Tracking (prd_discovery.py:632-642)

metadata: dict[str, Any] = {
    "template_id": resolved_template_id,
}
# Track mismatch for debugging
if template_id and template_id != resolved_template_id:
    metadata["requested_template_id"] = template_id

Excellent: This will help users debug why their PRD doesn't match expectations.

3. Project Template Override (prd_templates.py:794-800)

if template.id in self.templates:
    existing = self.templates[template.id]
    logger.warning(f"Template '{template.id}' overrides existing template")

Good: Clear warning when project templates override built-ins.


📊 Test Coverage

Test Files:

  • tests/planning/test_prd_templates.py - 727 lines
  • tests/cli/test_prd_template_commands.py - 215 lines

Coverage Areas:

  • ✅ Template data structures
  • ✅ Template rendering with Jinja2
  • ✅ Validation logic
  • ✅ File I/O and persistence
  • ✅ CLI commands (list/show/export/import)
  • ✅ Template resolution and fallback
  • ✅ Jinja2 custom filters

Test Quality: Comprehensive with good edge case coverage.


🎯 Minor Observations (Not Blocking)

1. User Notification When Fallback Occurs

Currently the CLI validates templates upfront (app.py:1295-1303), which is great. However, if a template is saved in discovery metadata but later deleted, users might not know why their PRD doesn't match expectations.

Suggestion for future PR: Add console warning in _build_prd_prompt when fallback occurs (similar to the logger.warning that's already there).

2. Template Caching Opportunity

Jinja2 templates are compiled on every render. For production use with many PRD generations, consider caching compiled templates with @lru_cache.

Assessment: Not a priority for Phase 1. Current implementation is fine.

3. Built-in Template Quality

The 5 built-in templates (standard, lean, enterprise, user-story-map, technical-spec) cover common use cases well. The section definitions are clear and purposeful.


🚀 Integration with v2 Roadmap

This PR directly supports Phase 1: CLI Completion goals:

  • ✅ Enhances cf prd generate with customizable output formats
  • ✅ Adds new CLI subcommand group (cf prd templates)
  • ✅ Maintains headless architecture
  • ✅ No dependencies on server/UI layers

Future Extension Points:

  • Phase 2: REST API endpoint for template management (thin adapter over core)
  • Phase 3: Web UI for template editing with live preview
  • Phase 4: Multi-agent templates (different sections generated by specialized agents)

📝 Summary

This is production-ready code. All critical issues from previous reviews have been addressed:

✅ Templates are actually used in PRD generation
✅ Workspace paths properly passed for project template discovery
✅ Security: Jinja2 autoescape enabled + validation before import
✅ Error handling: Graceful fallbacks with logging
✅ Testing: Comprehensive coverage (40+ unit tests, 12+ CLI tests)
✅ Architecture: Follows v2 principles perfectly
✅ Documentation: Clear docstrings and inline comments

No blocking issues identified.


🎉 Recommendation

APPROVE - Ready to Merge

This feature adds significant value to CodeFRAME's PRD generation capabilities while maintaining architectural integrity. The implementation is thoughtful, well-tested, and properly integrated.

Excellent work! 🚀


Reviewed against: CodeFRAME v2 Strategic Roadmap, CLAUDE.md guidelines, previous review feedback, and codebase patterns

format_template=s["format_template"],
required=s.get("required", True),
)
for s in data.get("sections", [])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low

Null sections (e.g., YAML sections: null) can cause iteration errors during loading and validation. Suggest normalizing sections to [] when loading and guarding (or early-returning) in validate_template before iterating.

Suggested change
for s in data.get("sections", [])
for s in data.get("sections") or []

🚀 Want me to fix this? Reply ex: "fix it for me".

Comment on lines +907 to +910
self.templates[template.id] = template

if persist:
self.persist_template(template)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low

When persist=True but workspace_path is None, the template is added to self.templates before persist_template raises ValueError, leaving inconsistent state. Consider calling persist_template before registering, or checking workspace_path upfront.

Suggested change
self.templates[template.id] = template
if persist:
self.persist_template(template)
if persist:
self.persist_template(template)
self.templates[template.id] = template

🚀 Want me to fix this? Reply ex: "fix it for me".

@frankbria frankbria merged commit 7fc6a53 into main Jan 31, 2026
16 checks passed
@frankbria frankbria deleted the feature/prd-templates-316 branch January 31, 2026 16:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Phase 1] PRD template system for customizable output formats

2 participants