Class: Aidp::Prompts::PromptTemplateManager
- Inherits:
-
Object
- Object
- Aidp::Prompts::PromptTemplateManager
- Defined in:
- lib/aidp/prompts/prompt_template_manager.rb
Overview
Manages dynamic prompt templates with support for:
- Template loading from YAML files
- Variable substitution with {placeholder} syntax
- Template versioning and caching
- User/project-level template customization
- Per issue #402: Database-backed versioned templates with feedback-based selection
Template search order: 0. Database versioned templates (for versionable categories like work_loop)
- Project-level: .aidp/prompts/
/ .yml - User-level: ~/.aidp/prompts/
/ .yml - Built-in: lib/aidp/prompts/defaults/
/ .yml
Constant Summary collapse
- TEMPLATE_EXT =
Template file extension
".yml"- CACHE_TTL =
Cache TTL for template metadata (5 minutes)
300- VERSIONED_CATEGORIES =
Categories that support database versioning
%w[work_loop].freeze
Instance Attribute Summary collapse
-
#cache ⇒ Object
readonly
Returns the value of attribute cache.
-
#project_dir ⇒ Object
readonly
Returns the value of attribute project_dir.
Instance Method Summary collapse
-
#clear_cache ⇒ Object
Clear all cached templates.
-
#customize_template(template_id) ⇒ String
Copy a template to project-level for customization.
-
#determine_source(path) ⇒ Symbol
Determine the source of a template based on its path.
-
#initialize(project_dir: Dir.pwd, version_manager: nil) ⇒ PromptTemplateManager
constructor
A new instance of PromptTemplateManager.
-
#list_templates ⇒ Array<Hash>
List all available templates.
-
#load_template(template_id, use_versioned: true) ⇒ Hash?
Load template metadata without rendering Per issue #402: Checks database versions first for versionable templates.
-
#load_versioned_template(template_id) ⇒ Hash?
Load template data from versioned storage.
-
#render(template_id, use_versioned: true, **variables) ⇒ String
Render a template with variable substitution Per issue #402: Checks database versions first for versionable templates.
-
#render_versioned(template_id, **variables) ⇒ String?
Render using a versioned template from the database.
-
#reset_template(template_id) ⇒ Boolean
Reset a customized template to default.
-
#search_paths ⇒ Object
Get search paths in order of precedence.
-
#template_exists?(template_id) ⇒ Boolean
Check if a template exists.
-
#template_info(template_id) ⇒ Hash?
Get template info.
-
#version_manager ⇒ Object
Lazily initialize version manager.
-
#versionable?(template_id) ⇒ Boolean
Check if a template category supports versioning.
Constructor Details
#initialize(project_dir: Dir.pwd, version_manager: nil) ⇒ PromptTemplateManager
Returns a new instance of PromptTemplateManager.
42 43 44 45 46 47 |
# File 'lib/aidp/prompts/prompt_template_manager.rb', line 42 def initialize(project_dir: Dir.pwd, version_manager: nil) @project_dir = project_dir @cache = {} @cache_timestamps = {} @version_manager_instance = version_manager end |
Instance Attribute Details
#cache ⇒ Object (readonly)
Returns the value of attribute cache.
40 41 42 |
# File 'lib/aidp/prompts/prompt_template_manager.rb', line 40 def cache @cache end |
#project_dir ⇒ Object (readonly)
Returns the value of attribute project_dir.
40 41 42 |
# File 'lib/aidp/prompts/prompt_template_manager.rb', line 40 def project_dir @project_dir end |
Instance Method Details
#clear_cache ⇒ Object
Clear all cached templates
298 299 300 301 302 |
# File 'lib/aidp/prompts/prompt_template_manager.rb', line 298 def clear_cache @cache.clear @cache_timestamps.clear Aidp.log_debug("prompt_template_manager", "cache_cleared") end |
#customize_template(template_id) ⇒ String
Copy a template to project-level for customization
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 |
# File 'lib/aidp/prompts/prompt_template_manager.rb', line 255 def customize_template(template_id) source_path = find_template_path(template_id) raise TemplateNotFoundError, "Template not found: #{template_id}" unless source_path dest_path = project_template_path(template_id) return dest_path if File.exist?(dest_path) FileUtils.mkdir_p(File.dirname(dest_path)) FileUtils.cp(source_path, dest_path) Aidp.log_info("prompt_template_manager", "template_customized", template_id: template_id, path: dest_path) # Clear cache for this template invalidate_cache("template:#{template_id}") dest_path end |
#determine_source(path) ⇒ Symbol
Determine the source of a template based on its path
317 318 319 320 321 322 323 324 325 |
# File 'lib/aidp/prompts/prompt_template_manager.rb', line 317 def determine_source(path) if path.start_with?(project_prompts_dir) :project elsif path.start_with?(user_prompts_dir) :user else :builtin end end |
#list_templates ⇒ Array<Hash>
List all available templates
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/aidp/prompts/prompt_template_manager.rb', line 195 def list_templates templates = [] search_paths.each do |base_path| next unless Dir.exist?(base_path) Dir.glob(File.join(base_path, "**", "*#{TEMPLATE_EXT}")).each do |path| relative_path = path.sub("#{base_path}/", "") template_id = relative_path.sub(TEMPLATE_EXT, "") # Only add if not already found (respects precedence) next if templates.any? { |t| t[:id] == template_id } template_data = load_yaml_template(path) next if template_data.nil? templates << { id: template_id, path: path, name: template_data["name"] || template_id, description: template_data["description"], version: template_data["version"], category: File.dirname(template_id) } end end templates.sort_by { |t| t[:id] } end |
#load_template(template_id, use_versioned: true) ⇒ Hash?
Load template metadata without rendering Per issue #402: Checks database versions first for versionable templates
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/aidp/prompts/prompt_template_manager.rb', line 130 def load_template(template_id, use_versioned: true) cache_key = "template:#{template_id}" # Check cache if cached_valid?(cache_key) Aidp.log_debug("prompt_template_manager", "cache_hit", template_id: template_id) return @cache[cache_key] end # Try versioned template first for versionable categories if use_versioned && versionable?(template_id) versioned = load_versioned_template(template_id) if versioned set_cache(cache_key, versioned) return versioned end end # Search for template in order of precedence (file-based) template_path = find_template_path(template_id) return nil unless template_path Aidp.log_debug("prompt_template_manager", "loading_template", template_id: template_id, path: template_path) template_data = load_yaml_template(template_path) set_cache(cache_key, template_data) template_data end |
#load_versioned_template(template_id) ⇒ Hash?
Load template data from versioned storage
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/aidp/prompts/prompt_template_manager.rb', line 166 def load_versioned_template(template_id) return nil unless versionable?(template_id) active = version_manager.active_version(template_id: template_id) return nil unless active Aidp.log_debug("prompt_template_manager", "loading_versioned_template", template_id: template_id, version_id: active[:id]) YAML.safe_load(active[:content], permitted_classes: [Symbol], aliases: true) rescue => e Aidp.log_warn("prompt_template_manager", "versioned_load_failed", template_id: template_id, error: e.) nil end |
#render(template_id, use_versioned: true, **variables) ⇒ String
Render a template with variable substitution Per issue #402: Checks database versions first for versionable templates
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/aidp/prompts/prompt_template_manager.rb', line 74 def render(template_id, use_versioned: true, **variables) Aidp.log_debug("prompt_template_manager", "rendering_template", template_id: template_id, variables: variables.keys, use_versioned: use_versioned) # Try versioned template first for versionable categories if use_versioned && versionable?(template_id) versioned_result = render_versioned(template_id, **variables) return versioned_result if versioned_result end template_data = load_template(template_id, use_versioned: use_versioned) raise TemplateNotFoundError, "Template not found: #{template_id}" if template_data.nil? prompt_text = template_data["prompt"] || template_data[:prompt] raise TemplateNotFoundError, "Template has no prompt: #{template_id}" if prompt_text.nil? substitute_variables(prompt_text, variables) end |
#render_versioned(template_id, **variables) ⇒ String?
Render using a versioned template from the database
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/aidp/prompts/prompt_template_manager.rb', line 100 def render_versioned(template_id, **variables) return nil unless versionable?(template_id) active = version_manager.active_version(template_id: template_id) return nil unless active Aidp.log_debug("prompt_template_manager", "rendering_versioned_template", template_id: template_id, version_id: active[:id], version_number: active[:version_number]) # Parse the stored YAML content template_data = YAML.safe_load(active[:content], permitted_classes: [Symbol], aliases: true) prompt_text = template_data["prompt"] || template_data[:prompt] return nil unless prompt_text substitute_variables(prompt_text, variables) rescue => e Aidp.log_warn("prompt_template_manager", "versioned_render_failed", template_id: template_id, error: e.) nil end |
#reset_template(template_id) ⇒ Boolean
Reset a customized template to default
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 |
# File 'lib/aidp/prompts/prompt_template_manager.rb', line 279 def reset_template(template_id) project_path = project_template_path(template_id) unless File.exist?(project_path) Aidp.log_debug("prompt_template_manager", "no_customization_to_reset", template_id: template_id) return false end FileUtils.rm(project_path) Aidp.log_info("prompt_template_manager", "template_reset", template_id: template_id) invalidate_cache("template:#{template_id}") true end |
#search_paths ⇒ Object
Get search paths in order of precedence
305 306 307 308 309 310 311 |
# File 'lib/aidp/prompts/prompt_template_manager.rb', line 305 def search_paths [ project_prompts_dir, user_prompts_dir, builtin_prompts_dir ] end |
#template_exists?(template_id) ⇒ Boolean
Check if a template exists
188 189 190 |
# File 'lib/aidp/prompts/prompt_template_manager.rb', line 188 def template_exists?(template_id) !find_template_path(template_id).nil? end |
#template_info(template_id) ⇒ Hash?
Get template info
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/aidp/prompts/prompt_template_manager.rb', line 229 def template_info(template_id) path = find_template_path(template_id) return nil unless path template_data = load_yaml_template(path) return nil if template_data.nil? source = determine_source(path) { id: template_id, path: path, source: source, name: template_data["name"], description: template_data["description"], version: template_data["version"], variables: extract_variables(template_data["prompt"]), schema: template_data["schema"], prompt_preview: truncate(template_data["prompt"], 500) } end |
#version_manager ⇒ Object
Lazily initialize version manager
50 51 52 53 54 55 |
# File 'lib/aidp/prompts/prompt_template_manager.rb', line 50 def version_manager @version_manager_instance ||= begin require_relative "template_version_manager" TemplateVersionManager.new(project_dir: @project_dir) end end |
#versionable?(template_id) ⇒ Boolean
Check if a template category supports versioning
61 62 63 64 |
# File 'lib/aidp/prompts/prompt_template_manager.rb', line 61 def versionable?(template_id) category = template_id.split("/").first VERSIONED_CATEGORIES.include?(category) end |