codeframe-ai 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codeframe/__init__.py +11 -0
- codeframe/__main__.py +20 -0
- codeframe/adapters/__init__.py +5 -0
- codeframe/adapters/e2b/__init__.py +13 -0
- codeframe/adapters/e2b/adapter.py +342 -0
- codeframe/adapters/e2b/budget.py +71 -0
- codeframe/adapters/e2b/credential_scanner.py +134 -0
- codeframe/adapters/llm/__init__.py +92 -0
- codeframe/adapters/llm/anthropic.py +414 -0
- codeframe/adapters/llm/base.py +444 -0
- codeframe/adapters/llm/mock.py +281 -0
- codeframe/adapters/llm/openai.py +483 -0
- codeframe/agents/__init__.py +8 -0
- codeframe/agents/dependency_resolver.py +714 -0
- codeframe/auth/__init__.py +16 -0
- codeframe/auth/api_key_router.py +238 -0
- codeframe/auth/api_keys.py +156 -0
- codeframe/auth/dependencies.py +358 -0
- codeframe/auth/manager.py +178 -0
- codeframe/auth/models.py +30 -0
- codeframe/auth/router.py +93 -0
- codeframe/auth/schemas.py +15 -0
- codeframe/auth/scopes.py +53 -0
- codeframe/cli/__init__.py +12 -0
- codeframe/cli/__main__.py +20 -0
- codeframe/cli/api_client.py +275 -0
- codeframe/cli/app.py +5688 -0
- codeframe/cli/auth.py +122 -0
- codeframe/cli/auth_commands.py +958 -0
- codeframe/cli/commands/__init__.py +5 -0
- codeframe/cli/config_commands.py +79 -0
- codeframe/cli/dashboard_commands.py +67 -0
- codeframe/cli/engines_commands.py +205 -0
- codeframe/cli/env_commands.py +409 -0
- codeframe/cli/helpers.py +56 -0
- codeframe/cli/hooks_commands.py +208 -0
- codeframe/cli/import_commands.py +129 -0
- codeframe/cli/pr_commands.py +549 -0
- codeframe/cli/proof_commands.py +415 -0
- codeframe/cli/stats_commands.py +311 -0
- codeframe/cli/telemetry_runtime.py +153 -0
- codeframe/cli/validators.py +123 -0
- codeframe/config/rate_limits.py +165 -0
- codeframe/core/__init__.py +15 -0
- codeframe/core/adapters/__init__.py +43 -0
- codeframe/core/adapters/agent_adapter.py +114 -0
- codeframe/core/adapters/builtin.py +326 -0
- codeframe/core/adapters/claude_code.py +62 -0
- codeframe/core/adapters/codex.py +393 -0
- codeframe/core/adapters/git_utils.py +40 -0
- codeframe/core/adapters/kilocode.py +126 -0
- codeframe/core/adapters/opencode.py +48 -0
- codeframe/core/adapters/streaming_chat.py +483 -0
- codeframe/core/adapters/subprocess_adapter.py +213 -0
- codeframe/core/adapters/verification_wrapper.py +269 -0
- codeframe/core/agent.py +2183 -0
- codeframe/core/agents_config.py +569 -0
- codeframe/core/api_key_service.py +211 -0
- codeframe/core/artifacts.py +428 -0
- codeframe/core/blocker_detection.py +218 -0
- codeframe/core/blockers.py +433 -0
- codeframe/core/checkpoints.py +481 -0
- codeframe/core/conductor.py +2255 -0
- codeframe/core/config.py +827 -0
- codeframe/core/config_watcher.py +268 -0
- codeframe/core/context.py +542 -0
- codeframe/core/context_packager.py +234 -0
- codeframe/core/credentials.py +735 -0
- codeframe/core/dependency_analyzer.py +229 -0
- codeframe/core/dependency_graph.py +290 -0
- codeframe/core/diagnostic_agent.py +712 -0
- codeframe/core/diagnostics.py +616 -0
- codeframe/core/editor.py +556 -0
- codeframe/core/engine_registry.py +256 -0
- codeframe/core/engine_stats.py +231 -0
- codeframe/core/environment.py +697 -0
- codeframe/core/events.py +375 -0
- codeframe/core/executor.py +1005 -0
- codeframe/core/fix_tracker.py +480 -0
- codeframe/core/gates.py +1322 -0
- codeframe/core/git.py +477 -0
- codeframe/core/github_connect_service.py +178 -0
- codeframe/core/github_integration_config.py +118 -0
- codeframe/core/github_issues_service.py +449 -0
- codeframe/core/hooks.py +184 -0
- codeframe/core/importers/__init__.py +1 -0
- codeframe/core/importers/ralph.py +540 -0
- codeframe/core/installer.py +650 -0
- codeframe/core/models.py +1026 -0
- codeframe/core/notifications_config.py +183 -0
- codeframe/core/planner.py +437 -0
- codeframe/core/prd.py +670 -0
- codeframe/core/prd_discovery.py +1118 -0
- codeframe/core/prd_stress_test.py +499 -0
- codeframe/core/progress.py +126 -0
- codeframe/core/proof/__init__.py +34 -0
- codeframe/core/proof/capture.py +79 -0
- codeframe/core/proof/evidence.py +56 -0
- codeframe/core/proof/ledger.py +574 -0
- codeframe/core/proof/models.py +162 -0
- codeframe/core/proof/obligations.py +103 -0
- codeframe/core/proof/runner.py +233 -0
- codeframe/core/proof/scope.py +81 -0
- codeframe/core/proof/stubs.py +156 -0
- codeframe/core/quick_fixes.py +558 -0
- codeframe/core/react_agent.py +1650 -0
- codeframe/core/reconciliation.py +183 -0
- codeframe/core/replay.py +788 -0
- codeframe/core/review.py +285 -0
- codeframe/core/runtime.py +1134 -0
- codeframe/core/sandbox/__init__.py +27 -0
- codeframe/core/sandbox/context.py +98 -0
- codeframe/core/sandbox/worktree.py +20 -0
- codeframe/core/schedule.py +396 -0
- codeframe/core/stall_detector.py +71 -0
- codeframe/core/stall_monitor.py +134 -0
- codeframe/core/state_machine.py +121 -0
- codeframe/core/streaming.py +502 -0
- codeframe/core/task_tree.py +400 -0
- codeframe/core/tasks.py +1022 -0
- codeframe/core/telemetry.py +232 -0
- codeframe/core/templates.py +221 -0
- codeframe/core/tools.py +942 -0
- codeframe/core/workspace.py +887 -0
- codeframe/core/worktrees.py +276 -0
- codeframe/git/__init__.py +5 -0
- codeframe/git/github_integration.py +505 -0
- codeframe/lib/__init__.py +0 -0
- codeframe/lib/audit_logger.py +248 -0
- codeframe/lib/metrics_tracker.py +800 -0
- codeframe/lib/quality/__init__.py +7 -0
- codeframe/lib/quality/complexity_analyzer.py +316 -0
- codeframe/lib/quality/owasp_patterns.py +284 -0
- codeframe/lib/quality/security_scanner.py +250 -0
- codeframe/lib/rate_limiter.py +312 -0
- codeframe/notifications/__init__.py +0 -0
- codeframe/notifications/webhook.py +380 -0
- codeframe/planning/__init__.py +30 -0
- codeframe/planning/issue_generator.py +219 -0
- codeframe/planning/prd_template_functions.py +137 -0
- codeframe/planning/prd_templates.py +975 -0
- codeframe/planning/task_scheduler.py +511 -0
- codeframe/planning/task_templates.py +533 -0
- codeframe/platform_store/__init__.py +5 -0
- codeframe/platform_store/database.py +277 -0
- codeframe/platform_store/repositories/__init__.py +24 -0
- codeframe/platform_store/repositories/api_key_repository.py +245 -0
- codeframe/platform_store/repositories/audit_repository.py +67 -0
- codeframe/platform_store/repositories/base.py +295 -0
- codeframe/platform_store/repositories/interactive_sessions.py +165 -0
- codeframe/platform_store/repositories/token_repository.py +598 -0
- codeframe/platform_store/repositories/workspace_registry_repository.py +175 -0
- codeframe/platform_store/schema_manager.py +321 -0
- codeframe/templates/AGENTS.md.default +94 -0
- codeframe/tui/__init__.py +5 -0
- codeframe/tui/app.py +256 -0
- codeframe/tui/data_service.py +103 -0
- codeframe/ui/__init__.py +0 -0
- codeframe/ui/dependencies.py +103 -0
- codeframe/ui/models.py +999 -0
- codeframe/ui/response_models.py +201 -0
- codeframe/ui/routers/__init__.py +5 -0
- codeframe/ui/routers/_helpers.py +29 -0
- codeframe/ui/routers/batches_v2.py +315 -0
- codeframe/ui/routers/blockers_v2.py +320 -0
- codeframe/ui/routers/checkpoints_v2.py +310 -0
- codeframe/ui/routers/costs_v2.py +322 -0
- codeframe/ui/routers/diagnose_v2.py +225 -0
- codeframe/ui/routers/discovery_v2.py +417 -0
- codeframe/ui/routers/environment_v2.py +284 -0
- codeframe/ui/routers/events_v2.py +75 -0
- codeframe/ui/routers/gates_v2.py +166 -0
- codeframe/ui/routers/git_v2.py +284 -0
- codeframe/ui/routers/github_integrations_v2.py +532 -0
- codeframe/ui/routers/interactive_sessions_v2.py +238 -0
- codeframe/ui/routers/pr_v2.py +709 -0
- codeframe/ui/routers/prd_v2.py +695 -0
- codeframe/ui/routers/proof_v2.py +755 -0
- codeframe/ui/routers/review_v2.py +360 -0
- codeframe/ui/routers/schedule_v2.py +214 -0
- codeframe/ui/routers/session_chat_ws.py +354 -0
- codeframe/ui/routers/settings_v2.py +562 -0
- codeframe/ui/routers/streaming_v2.py +155 -0
- codeframe/ui/routers/tasks_v2.py +1098 -0
- codeframe/ui/routers/templates_v2.py +232 -0
- codeframe/ui/routers/terminal_ws.py +267 -0
- codeframe/ui/routers/workspace_v2.py +527 -0
- codeframe/ui/server.py +568 -0
- codeframe/ui/shared.py +241 -0
- codeframe/workspace/__init__.py +5 -0
- codeframe/workspace/manager.py +249 -0
- codeframe_ai-0.9.0.dist-info/METADATA +517 -0
- codeframe_ai-0.9.0.dist-info/RECORD +197 -0
- codeframe_ai-0.9.0.dist-info/WHEEL +5 -0
- codeframe_ai-0.9.0.dist-info/entry_points.txt +3 -0
- codeframe_ai-0.9.0.dist-info/licenses/LICENSE +661 -0
- codeframe_ai-0.9.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,975 @@
|
|
|
1
|
+
"""PRD Template System for CodeFRAME.
|
|
2
|
+
|
|
3
|
+
This module provides a template system for customizable PRD output formats:
|
|
4
|
+
- PrdTemplateSection: Represents a single section with rendering template
|
|
5
|
+
- PrdTemplate: Contains template metadata and list of sections
|
|
6
|
+
- PrdTemplateManager: Manage and render templates
|
|
7
|
+
- BUILTIN_TEMPLATES: Predefined templates (standard, lean, enterprise, etc.)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Optional
|
|
14
|
+
|
|
15
|
+
import yaml
|
|
16
|
+
from jinja2 import Environment, BaseLoader, TemplateSyntaxError
|
|
17
|
+
|
|
18
|
+
from codeframe.planning.prd_template_functions import TEMPLATE_FUNCTIONS
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class PrdTemplateSection:
|
|
25
|
+
"""Individual section within a PRD template.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
id: Unique identifier for the section
|
|
29
|
+
title: Human-readable section title
|
|
30
|
+
source: Discovery data category to draw from (problem, users, features, etc.)
|
|
31
|
+
format_template: Jinja2 template string for rendering
|
|
32
|
+
required: Whether this section must be included (default: True)
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
id: str
|
|
36
|
+
title: str
|
|
37
|
+
source: str
|
|
38
|
+
format_template: str
|
|
39
|
+
required: bool = True
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class PrdTemplate:
|
|
44
|
+
"""Template for generating a PRD.
|
|
45
|
+
|
|
46
|
+
Attributes:
|
|
47
|
+
id: Unique identifier for the template
|
|
48
|
+
name: Human-readable name
|
|
49
|
+
version: Template version number
|
|
50
|
+
description: Description of when to use this template
|
|
51
|
+
sections: List of PrdTemplateSection objects
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
id: str
|
|
55
|
+
name: str
|
|
56
|
+
version: int
|
|
57
|
+
description: str
|
|
58
|
+
sections: list[PrdTemplateSection]
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def section_ids(self) -> list[str]:
|
|
62
|
+
"""Get list of section IDs."""
|
|
63
|
+
return [s.id for s in self.sections]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# Built-in templates
|
|
67
|
+
BUILTIN_TEMPLATES: list[PrdTemplate] = [
|
|
68
|
+
PrdTemplate(
|
|
69
|
+
id="standard",
|
|
70
|
+
name="Standard PRD",
|
|
71
|
+
version=1,
|
|
72
|
+
description="Default PRD format with executive summary, problem statement, "
|
|
73
|
+
"user personas, features, technical architecture, success metrics, and timeline.",
|
|
74
|
+
sections=[
|
|
75
|
+
PrdTemplateSection(
|
|
76
|
+
id="executive_summary",
|
|
77
|
+
title="Executive Summary",
|
|
78
|
+
source="problem",
|
|
79
|
+
format_template="""## Executive Summary
|
|
80
|
+
|
|
81
|
+
{{ problem | default('Project overview not yet defined.') }}
|
|
82
|
+
""",
|
|
83
|
+
),
|
|
84
|
+
PrdTemplateSection(
|
|
85
|
+
id="problem_statement",
|
|
86
|
+
title="Problem Statement",
|
|
87
|
+
source="problem",
|
|
88
|
+
format_template="""## Problem Statement
|
|
89
|
+
|
|
90
|
+
{{ problem | default('Problem statement not yet defined.') }}
|
|
91
|
+
|
|
92
|
+
### Business Justification
|
|
93
|
+
|
|
94
|
+
This solution addresses a critical need in the target market by solving the core problem outlined above.
|
|
95
|
+
""",
|
|
96
|
+
),
|
|
97
|
+
PrdTemplateSection(
|
|
98
|
+
id="user_personas",
|
|
99
|
+
title="User Personas",
|
|
100
|
+
source="users",
|
|
101
|
+
format_template="""## User Personas
|
|
102
|
+
|
|
103
|
+
### Target Users
|
|
104
|
+
|
|
105
|
+
{{ users | bullet_list if users else 'User personas not yet defined.' }}
|
|
106
|
+
""",
|
|
107
|
+
),
|
|
108
|
+
PrdTemplateSection(
|
|
109
|
+
id="features",
|
|
110
|
+
title="Features & Requirements",
|
|
111
|
+
source="features",
|
|
112
|
+
format_template="""## Features & Requirements
|
|
113
|
+
|
|
114
|
+
### Core Features
|
|
115
|
+
|
|
116
|
+
{{ features | numbered_list if features else 'Features not yet defined.' }}
|
|
117
|
+
|
|
118
|
+
### Functional Requirements
|
|
119
|
+
|
|
120
|
+
Each feature listed above represents a functional requirement that must be implemented to meet user needs.
|
|
121
|
+
""",
|
|
122
|
+
),
|
|
123
|
+
PrdTemplateSection(
|
|
124
|
+
id="technical_architecture",
|
|
125
|
+
title="Technical Architecture",
|
|
126
|
+
source="tech_stack",
|
|
127
|
+
format_template="""## Technical Architecture
|
|
128
|
+
|
|
129
|
+
### Technology Stack
|
|
130
|
+
|
|
131
|
+
{{ tech_stack | bullet_list if tech_stack else 'Technology stack not yet defined.' }}
|
|
132
|
+
|
|
133
|
+
### Constraints
|
|
134
|
+
|
|
135
|
+
{{ constraints | format_constraints if constraints else 'No specific constraints defined.' }}
|
|
136
|
+
""",
|
|
137
|
+
),
|
|
138
|
+
PrdTemplateSection(
|
|
139
|
+
id="success_metrics",
|
|
140
|
+
title="Success Metrics",
|
|
141
|
+
source="features",
|
|
142
|
+
format_template="""## Success Metrics
|
|
143
|
+
|
|
144
|
+
### Key Performance Indicators
|
|
145
|
+
|
|
146
|
+
- User adoption rate
|
|
147
|
+
- Feature completion rate
|
|
148
|
+
- System performance metrics
|
|
149
|
+
- User satisfaction scores
|
|
150
|
+
|
|
151
|
+
### Success Criteria
|
|
152
|
+
|
|
153
|
+
The project will be considered successful when all core features are implemented and user adoption targets are met.
|
|
154
|
+
""",
|
|
155
|
+
),
|
|
156
|
+
PrdTemplateSection(
|
|
157
|
+
id="timeline",
|
|
158
|
+
title="Timeline & Milestones",
|
|
159
|
+
source="features",
|
|
160
|
+
format_template="""## Timeline & Milestones
|
|
161
|
+
|
|
162
|
+
### Project Phases
|
|
163
|
+
|
|
164
|
+
1. **Discovery & Planning** - Requirements gathering and architecture design
|
|
165
|
+
2. **Development Phase 1** - Core feature implementation
|
|
166
|
+
3. **Development Phase 2** - Additional features and refinements
|
|
167
|
+
4. **Testing & QA** - Comprehensive testing and bug fixes
|
|
168
|
+
5. **Launch** - Deployment and user onboarding
|
|
169
|
+
|
|
170
|
+
### Milestones
|
|
171
|
+
|
|
172
|
+
Specific dates and milestones to be determined based on team capacity and priorities.
|
|
173
|
+
""",
|
|
174
|
+
),
|
|
175
|
+
],
|
|
176
|
+
),
|
|
177
|
+
PrdTemplate(
|
|
178
|
+
id="lean",
|
|
179
|
+
name="Lean PRD",
|
|
180
|
+
version=1,
|
|
181
|
+
description="Minimal viable PRD with only problem, users, and MVP features. "
|
|
182
|
+
"Best for quick iterations and early-stage projects.",
|
|
183
|
+
sections=[
|
|
184
|
+
PrdTemplateSection(
|
|
185
|
+
id="problem",
|
|
186
|
+
title="Problem",
|
|
187
|
+
source="problem",
|
|
188
|
+
format_template="""## Problem
|
|
189
|
+
|
|
190
|
+
{{ problem | default('Problem not yet defined.') }}
|
|
191
|
+
""",
|
|
192
|
+
),
|
|
193
|
+
PrdTemplateSection(
|
|
194
|
+
id="users",
|
|
195
|
+
title="Target Users",
|
|
196
|
+
source="users",
|
|
197
|
+
format_template="""## Target Users
|
|
198
|
+
|
|
199
|
+
{{ users | bullet_list if users else 'Users not yet defined.' }}
|
|
200
|
+
""",
|
|
201
|
+
),
|
|
202
|
+
PrdTemplateSection(
|
|
203
|
+
id="mvp_features",
|
|
204
|
+
title="MVP Features",
|
|
205
|
+
source="features",
|
|
206
|
+
format_template="""## MVP Features
|
|
207
|
+
|
|
208
|
+
{{ features | numbered_list if features else 'Features not yet defined.' }}
|
|
209
|
+
""",
|
|
210
|
+
),
|
|
211
|
+
],
|
|
212
|
+
),
|
|
213
|
+
PrdTemplate(
|
|
214
|
+
id="enterprise",
|
|
215
|
+
name="Enterprise PRD",
|
|
216
|
+
version=1,
|
|
217
|
+
description="Full formal PRD with compliance sections, traceability matrix, "
|
|
218
|
+
"stakeholder analysis, and risk assessment. Best for large organizations.",
|
|
219
|
+
sections=[
|
|
220
|
+
PrdTemplateSection(
|
|
221
|
+
id="executive_summary",
|
|
222
|
+
title="Executive Summary",
|
|
223
|
+
source="problem",
|
|
224
|
+
format_template="""## Executive Summary
|
|
225
|
+
|
|
226
|
+
### Overview
|
|
227
|
+
|
|
228
|
+
{{ problem | default('Project overview not yet defined.') }}
|
|
229
|
+
|
|
230
|
+
### Document Purpose
|
|
231
|
+
|
|
232
|
+
This Product Requirements Document (PRD) defines the requirements, scope, and specifications for the project.
|
|
233
|
+
""",
|
|
234
|
+
),
|
|
235
|
+
PrdTemplateSection(
|
|
236
|
+
id="stakeholder_analysis",
|
|
237
|
+
title="Stakeholder Analysis",
|
|
238
|
+
source="users",
|
|
239
|
+
format_template="""## Stakeholder Analysis
|
|
240
|
+
|
|
241
|
+
### Primary Stakeholders
|
|
242
|
+
|
|
243
|
+
{{ users | bullet_list if users else 'Stakeholders not yet defined.' }}
|
|
244
|
+
|
|
245
|
+
### Stakeholder Responsibilities
|
|
246
|
+
|
|
247
|
+
| Stakeholder | Role | Responsibility |
|
|
248
|
+
|-------------|------|----------------|
|
|
249
|
+
| Product Owner | Decision Maker | Final approval on requirements |
|
|
250
|
+
| Development Team | Implementer | Technical implementation |
|
|
251
|
+
| QA Team | Validator | Quality assurance |
|
|
252
|
+
""",
|
|
253
|
+
),
|
|
254
|
+
PrdTemplateSection(
|
|
255
|
+
id="problem_statement",
|
|
256
|
+
title="Problem Statement",
|
|
257
|
+
source="problem",
|
|
258
|
+
format_template="""## Problem Statement
|
|
259
|
+
|
|
260
|
+
### Current State
|
|
261
|
+
|
|
262
|
+
{{ problem | default('Current state analysis not yet completed.') }}
|
|
263
|
+
|
|
264
|
+
### Desired State
|
|
265
|
+
|
|
266
|
+
The solution will address the identified problems and deliver value to stakeholders.
|
|
267
|
+
|
|
268
|
+
### Gap Analysis
|
|
269
|
+
|
|
270
|
+
Detailed gap analysis between current and desired states to be documented during discovery.
|
|
271
|
+
""",
|
|
272
|
+
),
|
|
273
|
+
PrdTemplateSection(
|
|
274
|
+
id="user_personas",
|
|
275
|
+
title="User Personas",
|
|
276
|
+
source="users",
|
|
277
|
+
format_template="""## User Personas
|
|
278
|
+
|
|
279
|
+
{{ users | bullet_list if users else 'User personas not yet defined.' }}
|
|
280
|
+
""",
|
|
281
|
+
),
|
|
282
|
+
PrdTemplateSection(
|
|
283
|
+
id="requirements",
|
|
284
|
+
title="Requirements",
|
|
285
|
+
source="features",
|
|
286
|
+
format_template="""## Requirements
|
|
287
|
+
|
|
288
|
+
### Functional Requirements
|
|
289
|
+
|
|
290
|
+
{% if features %}
|
|
291
|
+
| ID | Requirement | Priority | Source |
|
|
292
|
+
|----|-------------|----------|--------|
|
|
293
|
+
{% for feature in features %}
|
|
294
|
+
| FR-{{ loop.index }} | {{ feature }} | P1 | Discovery |
|
|
295
|
+
{% endfor %}
|
|
296
|
+
{% else %}
|
|
297
|
+
Requirements not yet defined.
|
|
298
|
+
{% endif %}
|
|
299
|
+
|
|
300
|
+
### Non-Functional Requirements
|
|
301
|
+
|
|
302
|
+
| ID | Category | Requirement |
|
|
303
|
+
|----|----------|-------------|
|
|
304
|
+
| NFR-1 | Performance | System response time < 2 seconds |
|
|
305
|
+
| NFR-2 | Availability | 99.9% uptime SLA |
|
|
306
|
+
| NFR-3 | Security | Data encryption at rest and in transit |
|
|
307
|
+
""",
|
|
308
|
+
),
|
|
309
|
+
PrdTemplateSection(
|
|
310
|
+
id="technical_architecture",
|
|
311
|
+
title="Technical Architecture",
|
|
312
|
+
source="tech_stack",
|
|
313
|
+
format_template="""## Technical Architecture
|
|
314
|
+
|
|
315
|
+
### Technology Stack
|
|
316
|
+
|
|
317
|
+
{{ tech_stack | bullet_list if tech_stack else 'Technology stack not yet defined.' }}
|
|
318
|
+
|
|
319
|
+
### System Constraints
|
|
320
|
+
|
|
321
|
+
{{ constraints | format_constraints if constraints else 'No specific constraints defined.' }}
|
|
322
|
+
|
|
323
|
+
### Integration Points
|
|
324
|
+
|
|
325
|
+
Integration requirements to be documented during technical design phase.
|
|
326
|
+
""",
|
|
327
|
+
),
|
|
328
|
+
PrdTemplateSection(
|
|
329
|
+
id="risk_assessment",
|
|
330
|
+
title="Risk Assessment",
|
|
331
|
+
source="constraints",
|
|
332
|
+
format_template="""## Risk Assessment
|
|
333
|
+
|
|
334
|
+
### Identified Risks
|
|
335
|
+
|
|
336
|
+
| Risk | Impact | Likelihood | Mitigation |
|
|
337
|
+
|------|--------|------------|------------|
|
|
338
|
+
| Technical complexity | High | Medium | Phased implementation |
|
|
339
|
+
| Resource constraints | Medium | Medium | Priority-based scheduling |
|
|
340
|
+
| Scope creep | High | High | Strict change control |
|
|
341
|
+
|
|
342
|
+
### Compliance Considerations
|
|
343
|
+
|
|
344
|
+
{{ constraints | format_constraints if constraints else 'Compliance requirements to be documented.' }}
|
|
345
|
+
""",
|
|
346
|
+
required=False,
|
|
347
|
+
),
|
|
348
|
+
PrdTemplateSection(
|
|
349
|
+
id="success_metrics",
|
|
350
|
+
title="Success Metrics",
|
|
351
|
+
source="features",
|
|
352
|
+
format_template="""## Success Metrics
|
|
353
|
+
|
|
354
|
+
### Key Performance Indicators
|
|
355
|
+
|
|
356
|
+
| KPI | Target | Measurement Method |
|
|
357
|
+
|-----|--------|-------------------|
|
|
358
|
+
| User Adoption | 80% | Active user tracking |
|
|
359
|
+
| Feature Completion | 100% | Sprint tracking |
|
|
360
|
+
| Defect Rate | < 5% | Bug tracking |
|
|
361
|
+
| User Satisfaction | > 4.0/5.0 | User surveys |
|
|
362
|
+
""",
|
|
363
|
+
),
|
|
364
|
+
PrdTemplateSection(
|
|
365
|
+
id="timeline",
|
|
366
|
+
title="Timeline & Milestones",
|
|
367
|
+
source="features",
|
|
368
|
+
format_template="""## Timeline & Milestones
|
|
369
|
+
|
|
370
|
+
### Project Phases
|
|
371
|
+
|
|
372
|
+
| Phase | Description | Duration |
|
|
373
|
+
|-------|-------------|----------|
|
|
374
|
+
| Discovery | Requirements gathering | 2 weeks |
|
|
375
|
+
| Design | Technical architecture | 2 weeks |
|
|
376
|
+
| Development | Implementation | 8 weeks |
|
|
377
|
+
| Testing | QA and UAT | 2 weeks |
|
|
378
|
+
| Deployment | Production release | 1 week |
|
|
379
|
+
|
|
380
|
+
### Approval Gates
|
|
381
|
+
|
|
382
|
+
| Gate | Criteria | Approver |
|
|
383
|
+
|------|----------|----------|
|
|
384
|
+
| Requirements Sign-off | All requirements documented | Product Owner |
|
|
385
|
+
| Design Approval | Architecture approved | Tech Lead |
|
|
386
|
+
| Release Approval | All tests passing | QA Lead |
|
|
387
|
+
""",
|
|
388
|
+
),
|
|
389
|
+
PrdTemplateSection(
|
|
390
|
+
id="traceability",
|
|
391
|
+
title="Traceability Matrix",
|
|
392
|
+
source="features",
|
|
393
|
+
format_template="""## Traceability Matrix
|
|
394
|
+
|
|
395
|
+
This section maps requirements to test cases and implementation components.
|
|
396
|
+
|
|
397
|
+
| Requirement ID | Feature | Test Case | Component |
|
|
398
|
+
|----------------|---------|-----------|-----------|
|
|
399
|
+
{% if features %}
|
|
400
|
+
{% for feature in features %}
|
|
401
|
+
| FR-{{ loop.index }} | {{ feature }} | TC-{{ loop.index }} | TBD |
|
|
402
|
+
{% endfor %}
|
|
403
|
+
{% else %}
|
|
404
|
+
| - | Requirements not yet defined | - | - |
|
|
405
|
+
{% endif %}
|
|
406
|
+
""",
|
|
407
|
+
required=False,
|
|
408
|
+
),
|
|
409
|
+
],
|
|
410
|
+
),
|
|
411
|
+
PrdTemplate(
|
|
412
|
+
id="user-story-map",
|
|
413
|
+
name="User Story Map PRD",
|
|
414
|
+
version=1,
|
|
415
|
+
description="Organized around user journeys with story mapping structure. "
|
|
416
|
+
"Best for agile teams focused on user experience.",
|
|
417
|
+
sections=[
|
|
418
|
+
PrdTemplateSection(
|
|
419
|
+
id="overview",
|
|
420
|
+
title="Overview",
|
|
421
|
+
source="problem",
|
|
422
|
+
format_template="""## Overview
|
|
423
|
+
|
|
424
|
+
{{ problem | default('Project overview not yet defined.') }}
|
|
425
|
+
""",
|
|
426
|
+
),
|
|
427
|
+
PrdTemplateSection(
|
|
428
|
+
id="user_activities",
|
|
429
|
+
title="User Activities",
|
|
430
|
+
source="users",
|
|
431
|
+
format_template="""## User Activities
|
|
432
|
+
|
|
433
|
+
### Primary Users
|
|
434
|
+
|
|
435
|
+
{{ users | bullet_list if users else 'Users not yet defined.' }}
|
|
436
|
+
|
|
437
|
+
### User Goals
|
|
438
|
+
|
|
439
|
+
Users want to accomplish specific goals efficiently and effectively.
|
|
440
|
+
""",
|
|
441
|
+
),
|
|
442
|
+
PrdTemplateSection(
|
|
443
|
+
id="user_stories",
|
|
444
|
+
title="User Stories",
|
|
445
|
+
source="features",
|
|
446
|
+
format_template="""## User Stories
|
|
447
|
+
|
|
448
|
+
{% if features %}
|
|
449
|
+
{% for feature in features %}
|
|
450
|
+
### Story {{ loop.index }}: {{ feature }}
|
|
451
|
+
|
|
452
|
+
**As a** user
|
|
453
|
+
**I want to** {{ feature }}
|
|
454
|
+
**So that** I can accomplish my goals effectively
|
|
455
|
+
|
|
456
|
+
**Acceptance Criteria:**
|
|
457
|
+
- [ ] Feature is implemented as specified
|
|
458
|
+
- [ ] Feature is tested and working
|
|
459
|
+
- [ ] Feature is documented
|
|
460
|
+
|
|
461
|
+
{% endfor %}
|
|
462
|
+
{% else %}
|
|
463
|
+
User stories not yet defined.
|
|
464
|
+
{% endif %}
|
|
465
|
+
""",
|
|
466
|
+
),
|
|
467
|
+
PrdTemplateSection(
|
|
468
|
+
id="release_plan",
|
|
469
|
+
title="Release Plan",
|
|
470
|
+
source="features",
|
|
471
|
+
format_template="""## Release Plan
|
|
472
|
+
|
|
473
|
+
### MVP (Release 1)
|
|
474
|
+
|
|
475
|
+
Core features to be included in initial release.
|
|
476
|
+
|
|
477
|
+
{{ features | bullet_list if features else 'Features not yet prioritized.' }}
|
|
478
|
+
|
|
479
|
+
### Future Releases
|
|
480
|
+
|
|
481
|
+
Additional features and enhancements for subsequent releases.
|
|
482
|
+
""",
|
|
483
|
+
),
|
|
484
|
+
],
|
|
485
|
+
),
|
|
486
|
+
PrdTemplate(
|
|
487
|
+
id="technical-spec",
|
|
488
|
+
name="Technical Specification",
|
|
489
|
+
version=1,
|
|
490
|
+
description="Focused on technical requirements, architecture diagrams, "
|
|
491
|
+
"API specifications, and data models. Best for technical audiences.",
|
|
492
|
+
sections=[
|
|
493
|
+
PrdTemplateSection(
|
|
494
|
+
id="overview",
|
|
495
|
+
title="Technical Overview",
|
|
496
|
+
source="problem",
|
|
497
|
+
format_template="""## Technical Overview
|
|
498
|
+
|
|
499
|
+
### Purpose
|
|
500
|
+
|
|
501
|
+
{{ problem | default('Technical purpose not yet defined.') }}
|
|
502
|
+
|
|
503
|
+
### Scope
|
|
504
|
+
|
|
505
|
+
This document defines the technical specifications for the system.
|
|
506
|
+
""",
|
|
507
|
+
),
|
|
508
|
+
PrdTemplateSection(
|
|
509
|
+
id="architecture",
|
|
510
|
+
title="System Architecture",
|
|
511
|
+
source="tech_stack",
|
|
512
|
+
format_template="""## System Architecture
|
|
513
|
+
|
|
514
|
+
### Technology Stack
|
|
515
|
+
|
|
516
|
+
{{ tech_stack | bullet_list if tech_stack else 'Technology stack not yet defined.' }}
|
|
517
|
+
|
|
518
|
+
### Architecture Overview
|
|
519
|
+
|
|
520
|
+
```
|
|
521
|
+
+-------------------+
|
|
522
|
+
| Frontend UI |
|
|
523
|
+
+-------------------+
|
|
524
|
+
|
|
|
525
|
+
+-------------------+
|
|
526
|
+
| API Gateway |
|
|
527
|
+
+-------------------+
|
|
528
|
+
|
|
|
529
|
+
+-------------------+
|
|
530
|
+
| Business Logic |
|
|
531
|
+
+-------------------+
|
|
532
|
+
|
|
|
533
|
+
+-------------------+
|
|
534
|
+
| Data Layer |
|
|
535
|
+
+-------------------+
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### Component Diagram
|
|
539
|
+
|
|
540
|
+
Components to be detailed during technical design.
|
|
541
|
+
""",
|
|
542
|
+
),
|
|
543
|
+
PrdTemplateSection(
|
|
544
|
+
id="api_specification",
|
|
545
|
+
title="API Specification",
|
|
546
|
+
source="features",
|
|
547
|
+
format_template="""## API Specification
|
|
548
|
+
|
|
549
|
+
### Endpoints
|
|
550
|
+
|
|
551
|
+
{% if features %}
|
|
552
|
+
| Endpoint | Method | Description |
|
|
553
|
+
|----------|--------|-------------|
|
|
554
|
+
{% for feature in features %}
|
|
555
|
+
| /api/{{ feature | lower | replace(' ', '-') }} | GET/POST | {{ feature }} |
|
|
556
|
+
{% endfor %}
|
|
557
|
+
{% else %}
|
|
558
|
+
API endpoints not yet defined.
|
|
559
|
+
{% endif %}
|
|
560
|
+
|
|
561
|
+
### Authentication
|
|
562
|
+
|
|
563
|
+
API authentication method to be determined.
|
|
564
|
+
|
|
565
|
+
### Rate Limiting
|
|
566
|
+
|
|
567
|
+
Rate limiting policies to be defined based on usage patterns.
|
|
568
|
+
""",
|
|
569
|
+
),
|
|
570
|
+
PrdTemplateSection(
|
|
571
|
+
id="data_models",
|
|
572
|
+
title="Data Models",
|
|
573
|
+
source="features",
|
|
574
|
+
format_template="""## Data Models
|
|
575
|
+
|
|
576
|
+
### Entity Relationship
|
|
577
|
+
|
|
578
|
+
Core entities and their relationships to be documented.
|
|
579
|
+
|
|
580
|
+
### Database Schema
|
|
581
|
+
|
|
582
|
+
Schema design to be completed during technical design phase.
|
|
583
|
+
|
|
584
|
+
{{ constraints | format_constraints if constraints else 'Database constraints not yet defined.' }}
|
|
585
|
+
""",
|
|
586
|
+
),
|
|
587
|
+
PrdTemplateSection(
|
|
588
|
+
id="security",
|
|
589
|
+
title="Security Considerations",
|
|
590
|
+
source="constraints",
|
|
591
|
+
format_template="""## Security Considerations
|
|
592
|
+
|
|
593
|
+
### Authentication & Authorization
|
|
594
|
+
|
|
595
|
+
- Authentication method: TBD
|
|
596
|
+
- Authorization model: Role-based access control (RBAC)
|
|
597
|
+
|
|
598
|
+
### Data Protection
|
|
599
|
+
|
|
600
|
+
- Encryption at rest
|
|
601
|
+
- Encryption in transit (TLS 1.3)
|
|
602
|
+
- Data retention policies
|
|
603
|
+
|
|
604
|
+
### Compliance
|
|
605
|
+
|
|
606
|
+
{{ constraints | format_constraints if constraints else 'Compliance requirements to be documented.' }}
|
|
607
|
+
""",
|
|
608
|
+
),
|
|
609
|
+
PrdTemplateSection(
|
|
610
|
+
id="performance",
|
|
611
|
+
title="Performance Requirements",
|
|
612
|
+
source="constraints",
|
|
613
|
+
format_template="""## Performance Requirements
|
|
614
|
+
|
|
615
|
+
### Response Time
|
|
616
|
+
|
|
617
|
+
- API response time: < 200ms (p95)
|
|
618
|
+
- Page load time: < 2 seconds
|
|
619
|
+
|
|
620
|
+
### Throughput
|
|
621
|
+
|
|
622
|
+
- Target: 1000 requests/second
|
|
623
|
+
|
|
624
|
+
### Scalability
|
|
625
|
+
|
|
626
|
+
- Horizontal scaling capability
|
|
627
|
+
- Auto-scaling based on load
|
|
628
|
+
|
|
629
|
+
{{ constraints | format_constraints if constraints else 'Additional performance constraints to be defined.' }}
|
|
630
|
+
""",
|
|
631
|
+
),
|
|
632
|
+
],
|
|
633
|
+
),
|
|
634
|
+
]
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def get_global_template_dir() -> Path:
|
|
638
|
+
"""Get the global PRD template directory.
|
|
639
|
+
|
|
640
|
+
Returns:
|
|
641
|
+
Path to ~/.codeframe/templates/prd/
|
|
642
|
+
"""
|
|
643
|
+
return Path.home() / ".codeframe" / "templates" / "prd"
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
def get_project_template_dir(workspace_path: Optional[Path] = None) -> Path:
|
|
647
|
+
"""Get the project-level PRD template directory.
|
|
648
|
+
|
|
649
|
+
Args:
|
|
650
|
+
workspace_path: Optional workspace path (defaults to current directory)
|
|
651
|
+
|
|
652
|
+
Returns:
|
|
653
|
+
Path to .codeframe/templates/prd/
|
|
654
|
+
"""
|
|
655
|
+
base = workspace_path or Path.cwd()
|
|
656
|
+
return base / ".codeframe" / "templates" / "prd"
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
def save_template_to_file(template: PrdTemplate, path: Path) -> None:
|
|
660
|
+
"""Save a template to a YAML file.
|
|
661
|
+
|
|
662
|
+
Args:
|
|
663
|
+
template: Template to save
|
|
664
|
+
path: File path to save to
|
|
665
|
+
"""
|
|
666
|
+
# Convert to dictionary for YAML serialization
|
|
667
|
+
data = {
|
|
668
|
+
"id": template.id,
|
|
669
|
+
"name": template.name,
|
|
670
|
+
"version": template.version,
|
|
671
|
+
"description": template.description,
|
|
672
|
+
"sections": [
|
|
673
|
+
{
|
|
674
|
+
"id": s.id,
|
|
675
|
+
"title": s.title,
|
|
676
|
+
"source": s.source,
|
|
677
|
+
"format_template": s.format_template,
|
|
678
|
+
"required": s.required,
|
|
679
|
+
}
|
|
680
|
+
for s in template.sections
|
|
681
|
+
],
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
# Ensure parent directory exists
|
|
685
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
686
|
+
|
|
687
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
688
|
+
yaml.safe_dump(data, f, default_flow_style=False, allow_unicode=True)
|
|
689
|
+
|
|
690
|
+
logger.debug(f"Saved template to {path}")
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
def load_template_from_file(path: Path) -> PrdTemplate:
|
|
694
|
+
"""Load a template from a YAML file.
|
|
695
|
+
|
|
696
|
+
Args:
|
|
697
|
+
path: File path to load from
|
|
698
|
+
|
|
699
|
+
Returns:
|
|
700
|
+
Loaded PrdTemplate
|
|
701
|
+
|
|
702
|
+
Raises:
|
|
703
|
+
FileNotFoundError: If file doesn't exist
|
|
704
|
+
ValueError: If file is not a YAML file
|
|
705
|
+
yaml.YAMLError: If file is not valid YAML
|
|
706
|
+
"""
|
|
707
|
+
# Resolve to absolute path and validate
|
|
708
|
+
path = path.resolve()
|
|
709
|
+
if not path.is_file():
|
|
710
|
+
raise FileNotFoundError(f"Template file not found: {path}")
|
|
711
|
+
|
|
712
|
+
if path.suffix.lower() not in [".yaml", ".yml"]:
|
|
713
|
+
raise ValueError(f"Template must be a YAML file, got: {path.suffix}")
|
|
714
|
+
|
|
715
|
+
with open(path, encoding="utf-8") as f:
|
|
716
|
+
data = yaml.safe_load(f)
|
|
717
|
+
|
|
718
|
+
if not data:
|
|
719
|
+
raise ValueError(f"Template file is empty or invalid: {path}")
|
|
720
|
+
|
|
721
|
+
sections = [
|
|
722
|
+
PrdTemplateSection(
|
|
723
|
+
id=s["id"],
|
|
724
|
+
title=s["title"],
|
|
725
|
+
source=s["source"],
|
|
726
|
+
format_template=s["format_template"],
|
|
727
|
+
required=s.get("required", True),
|
|
728
|
+
)
|
|
729
|
+
for s in data.get("sections", [])
|
|
730
|
+
]
|
|
731
|
+
|
|
732
|
+
return PrdTemplate(
|
|
733
|
+
id=data["id"],
|
|
734
|
+
name=data["name"],
|
|
735
|
+
version=data.get("version", 1),
|
|
736
|
+
description=data.get("description", ""),
|
|
737
|
+
sections=sections,
|
|
738
|
+
)
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
class PrdTemplateManager:
|
|
742
|
+
"""Manager for PRD templates.
|
|
743
|
+
|
|
744
|
+
Handles:
|
|
745
|
+
- Loading and storing templates
|
|
746
|
+
- Retrieving templates by ID
|
|
747
|
+
- Validating template structure
|
|
748
|
+
- Rendering templates with discovery data
|
|
749
|
+
- Importing/exporting templates
|
|
750
|
+
"""
|
|
751
|
+
|
|
752
|
+
def __init__(self, workspace_path: Optional[Path] = None):
|
|
753
|
+
"""Initialize with built-in templates.
|
|
754
|
+
|
|
755
|
+
Args:
|
|
756
|
+
workspace_path: Optional workspace path for project templates
|
|
757
|
+
"""
|
|
758
|
+
self.templates: dict[str, PrdTemplate] = {}
|
|
759
|
+
self.workspace_path = workspace_path
|
|
760
|
+
|
|
761
|
+
# Load built-in templates
|
|
762
|
+
for template in BUILTIN_TEMPLATES:
|
|
763
|
+
self.templates[template.id] = template
|
|
764
|
+
|
|
765
|
+
# Load custom templates from global directory
|
|
766
|
+
self._load_from_directory(get_global_template_dir())
|
|
767
|
+
|
|
768
|
+
# Load project templates (override global)
|
|
769
|
+
if workspace_path:
|
|
770
|
+
self._load_from_directory(get_project_template_dir(workspace_path))
|
|
771
|
+
|
|
772
|
+
# Set up Jinja2 environment with autoescape for defense-in-depth
|
|
773
|
+
# PRDs are markdown but could be rendered to HTML downstream
|
|
774
|
+
self._env = Environment(loader=BaseLoader(), autoescape=True)
|
|
775
|
+
for name, func in TEMPLATE_FUNCTIONS.items():
|
|
776
|
+
self._env.filters[name] = func
|
|
777
|
+
|
|
778
|
+
logger.info(f"PrdTemplateManager initialized with {len(self.templates)} templates")
|
|
779
|
+
|
|
780
|
+
def _load_from_directory(self, directory: Path) -> None:
|
|
781
|
+
"""Load templates from a directory.
|
|
782
|
+
|
|
783
|
+
Args:
|
|
784
|
+
directory: Directory containing YAML template files
|
|
785
|
+
"""
|
|
786
|
+
if not directory.exists():
|
|
787
|
+
return
|
|
788
|
+
|
|
789
|
+
# Support both .yaml and .yml extensions
|
|
790
|
+
for pattern in ("*.yaml", "*.yml"):
|
|
791
|
+
for path in directory.glob(pattern):
|
|
792
|
+
try:
|
|
793
|
+
template = load_template_from_file(path)
|
|
794
|
+
if template.id in self.templates:
|
|
795
|
+
existing = self.templates[template.id]
|
|
796
|
+
logger.warning(
|
|
797
|
+
f"Template '{template.id}' from {path} overrides "
|
|
798
|
+
f"existing template '{existing.name}'"
|
|
799
|
+
)
|
|
800
|
+
self.templates[template.id] = template
|
|
801
|
+
logger.debug(f"Loaded template from {path}")
|
|
802
|
+
except Exception as e:
|
|
803
|
+
logger.warning(f"Failed to load template from {path}: {e}")
|
|
804
|
+
|
|
805
|
+
def get_template(self, template_id: str) -> Optional[PrdTemplate]:
|
|
806
|
+
"""Get a template by ID.
|
|
807
|
+
|
|
808
|
+
Args:
|
|
809
|
+
template_id: Template identifier
|
|
810
|
+
|
|
811
|
+
Returns:
|
|
812
|
+
PrdTemplate if found, None otherwise
|
|
813
|
+
"""
|
|
814
|
+
return self.templates.get(template_id)
|
|
815
|
+
|
|
816
|
+
def list_templates(self) -> list[PrdTemplate]:
|
|
817
|
+
"""List all available templates.
|
|
818
|
+
|
|
819
|
+
Returns:
|
|
820
|
+
List of PrdTemplate objects sorted by name
|
|
821
|
+
"""
|
|
822
|
+
return sorted(self.templates.values(), key=lambda t: t.name or "")
|
|
823
|
+
|
|
824
|
+
def validate_template(self, template: PrdTemplate) -> list[str]:
|
|
825
|
+
"""Validate a template structure.
|
|
826
|
+
|
|
827
|
+
Args:
|
|
828
|
+
template: Template to validate
|
|
829
|
+
|
|
830
|
+
Returns:
|
|
831
|
+
List of validation error messages (empty if valid)
|
|
832
|
+
"""
|
|
833
|
+
errors = []
|
|
834
|
+
|
|
835
|
+
# Check required fields
|
|
836
|
+
if not template.id:
|
|
837
|
+
errors.append("Template ID is required")
|
|
838
|
+
|
|
839
|
+
if not template.name:
|
|
840
|
+
errors.append("Template name is required")
|
|
841
|
+
|
|
842
|
+
if not template.sections:
|
|
843
|
+
errors.append("Template must have at least one section")
|
|
844
|
+
|
|
845
|
+
# Validate each section's Jinja2 syntax
|
|
846
|
+
for section in template.sections:
|
|
847
|
+
try:
|
|
848
|
+
self._env.from_string(section.format_template)
|
|
849
|
+
except TemplateSyntaxError as e:
|
|
850
|
+
errors.append(f"Section '{section.id}' has invalid Jinja2 syntax: {e}")
|
|
851
|
+
|
|
852
|
+
return errors
|
|
853
|
+
|
|
854
|
+
def render_template(
|
|
855
|
+
self, template: PrdTemplate, discovery_data: dict[str, Any]
|
|
856
|
+
) -> str:
|
|
857
|
+
"""Render a template with discovery data.
|
|
858
|
+
|
|
859
|
+
Args:
|
|
860
|
+
template: Template to render
|
|
861
|
+
discovery_data: Discovery data dictionary with keys like
|
|
862
|
+
problem, users, features, constraints, tech_stack
|
|
863
|
+
|
|
864
|
+
Returns:
|
|
865
|
+
Rendered PRD content as markdown string
|
|
866
|
+
"""
|
|
867
|
+
sections = []
|
|
868
|
+
|
|
869
|
+
for section in template.sections:
|
|
870
|
+
try:
|
|
871
|
+
jinja_template = self._env.from_string(section.format_template)
|
|
872
|
+
rendered = jinja_template.render(**discovery_data)
|
|
873
|
+
sections.append(rendered.strip())
|
|
874
|
+
except TemplateSyntaxError as e:
|
|
875
|
+
logger.error(f"Template syntax error in section {section.id}: {e}")
|
|
876
|
+
sections.append(f"## {section.title}\n\n*Template syntax error: {e}*")
|
|
877
|
+
except (KeyboardInterrupt, SystemExit):
|
|
878
|
+
raise # Don't catch these
|
|
879
|
+
except Exception as e:
|
|
880
|
+
logger.error(f"Failed to render section {section.id}: {e}", exc_info=True)
|
|
881
|
+
sections.append(f"## {section.title}\n\n*Error rendering section. Check logs for details.*")
|
|
882
|
+
|
|
883
|
+
# Join with double newlines for proper markdown separation
|
|
884
|
+
return "\n\n".join(sections)
|
|
885
|
+
|
|
886
|
+
def import_template(self, source_path: Path, persist: bool = False) -> PrdTemplate:
|
|
887
|
+
"""Import a template from a file.
|
|
888
|
+
|
|
889
|
+
Args:
|
|
890
|
+
source_path: Path to YAML template file
|
|
891
|
+
persist: If True, save template to the project template directory
|
|
892
|
+
|
|
893
|
+
Returns:
|
|
894
|
+
Imported template
|
|
895
|
+
|
|
896
|
+
Raises:
|
|
897
|
+
FileNotFoundError: If source file doesn't exist
|
|
898
|
+
ValueError: If template validation fails
|
|
899
|
+
"""
|
|
900
|
+
template = load_template_from_file(source_path)
|
|
901
|
+
|
|
902
|
+
# Validate before registering
|
|
903
|
+
errors = self.validate_template(template)
|
|
904
|
+
if errors:
|
|
905
|
+
raise ValueError(f"Invalid template: {', '.join(errors)}")
|
|
906
|
+
|
|
907
|
+
self.templates[template.id] = template
|
|
908
|
+
|
|
909
|
+
if persist:
|
|
910
|
+
self.persist_template(template)
|
|
911
|
+
|
|
912
|
+
logger.info(f"Imported template: {template.id}")
|
|
913
|
+
return template
|
|
914
|
+
|
|
915
|
+
def persist_template(self, template: PrdTemplate, to_project: bool = True) -> Path:
|
|
916
|
+
"""Persist a template to disk.
|
|
917
|
+
|
|
918
|
+
Saves the template to either the project template directory (.codeframe/templates/prd/)
|
|
919
|
+
or the global template directory (~/.codeframe/templates/prd/).
|
|
920
|
+
|
|
921
|
+
Args:
|
|
922
|
+
template: Template to persist
|
|
923
|
+
to_project: If True, save to project directory; if False, save to global
|
|
924
|
+
|
|
925
|
+
Returns:
|
|
926
|
+
Path where template was saved
|
|
927
|
+
|
|
928
|
+
Raises:
|
|
929
|
+
ValueError: If to_project=True but no workspace_path configured
|
|
930
|
+
"""
|
|
931
|
+
if to_project:
|
|
932
|
+
if self.workspace_path is None:
|
|
933
|
+
raise ValueError(
|
|
934
|
+
"Cannot persist to project: no workspace_path configured. "
|
|
935
|
+
"Either set to_project=False or initialize PrdTemplateManager with workspace_path."
|
|
936
|
+
)
|
|
937
|
+
target_dir = get_project_template_dir(self.workspace_path)
|
|
938
|
+
else:
|
|
939
|
+
target_dir = get_global_template_dir()
|
|
940
|
+
|
|
941
|
+
# Ensure directory exists
|
|
942
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
943
|
+
|
|
944
|
+
# Save template
|
|
945
|
+
output_path = target_dir / f"{template.id}.yaml"
|
|
946
|
+
save_template_to_file(template, output_path)
|
|
947
|
+
|
|
948
|
+
logger.info(f"Persisted template '{template.id}' to {output_path}")
|
|
949
|
+
return output_path
|
|
950
|
+
|
|
951
|
+
def export_template(self, template_id: str, output_path: Path) -> None:
|
|
952
|
+
"""Export a template to a file.
|
|
953
|
+
|
|
954
|
+
Args:
|
|
955
|
+
template_id: ID of template to export
|
|
956
|
+
output_path: Path to save the template
|
|
957
|
+
|
|
958
|
+
Raises:
|
|
959
|
+
ValueError: If template not found
|
|
960
|
+
"""
|
|
961
|
+
template = self.get_template(template_id)
|
|
962
|
+
if template is None:
|
|
963
|
+
raise ValueError(f"Template '{template_id}' not found")
|
|
964
|
+
|
|
965
|
+
save_template_to_file(template, output_path)
|
|
966
|
+
logger.info(f"Exported template '{template_id}' to {output_path}")
|
|
967
|
+
|
|
968
|
+
def register_template(self, template: PrdTemplate) -> None:
|
|
969
|
+
"""Register a custom template.
|
|
970
|
+
|
|
971
|
+
Args:
|
|
972
|
+
template: Template to register
|
|
973
|
+
"""
|
|
974
|
+
self.templates[template.id] = template
|
|
975
|
+
logger.info(f"Registered template: {template.id}")
|