Class: Aidp::Prompts::TemplateVersionManager
- Inherits:
-
Object
- Object
- Aidp::Prompts::TemplateVersionManager
- Defined in:
- lib/aidp/prompts/template_version_manager.rb
Overview
Manages versioned prompt templates with feedback-based selection
Per issue #402:
- Positive feedback: Count votes, prioritize for future use
- Negative feedback: Trigger AGD to create new variant
- Retain last 5 versions, ensuring at least 2 positive-feedback versions
- Per-project feedback tracking
- Focus on work-loop templates initially
Constant Summary collapse
- VERSIONED_CATEGORIES =
Categories eligible for versioning (focus on work-loop initially)
%w[work_loop].freeze
Instance Attribute Summary collapse
-
#project_dir ⇒ Object
readonly
Returns the value of attribute project_dir.
-
#repository ⇒ Object
readonly
Returns the value of attribute repository.
-
#template_manager ⇒ Object
readonly
Returns the value of attribute template_manager.
Instance Method Summary collapse
-
#activate_version(version_id:) ⇒ Hash
Activate a specific version.
-
#active_version(template_id:) ⇒ Hash?
Get the currently active version.
-
#best_version(template_id:) ⇒ Hash?
Get the best template version to use Prioritizes versions with higher positive votes.
-
#create_evolved_version(template_id:, new_content:, parent_version_id:, metadata: {}) ⇒ Hash
Evolve a template by creating a new AGD-generated variant Called by TemplateEvolver after AI generates improved content.
-
#initialize(project_dir: Dir.pwd, repository: nil, template_manager: nil) ⇒ TemplateVersionManager
constructor
A new instance of TemplateVersionManager.
-
#initialize_versioning(template_id:) ⇒ Hash
Initialize versioning for a template by importing current content.
-
#list_versions(template_id:, limit: 20) ⇒ Array<Hash>
List all versions for a template.
-
#record_negative_feedback(template_id:, suggestions: [], context: {}, evolve_on_negative: true) ⇒ Hash
Record negative feedback for active template version Triggers AGD-based evolution when evolve_on_negative is true.
-
#record_positive_feedback(template_id:) ⇒ Hash
Record positive feedback for active template version.
-
#render_versioned(template_id, **variables) ⇒ String?
Render a versioned template, using the active version if available.
-
#valid_template_id?(template_id) ⇒ Boolean
Validate template_id format (must be "category/name").
-
#version_stats(template_id:) ⇒ Hash
Get version statistics for a template.
-
#versionable?(template_id) ⇒ Boolean
Check if a template is eligible for versioning.
-
#versioned_template_ids ⇒ Array<String>
Get all template IDs that have versioning enabled.
-
#versions_needing_evolution(template_id: nil) ⇒ Array<Hash>
Get versions that need evolution (have negative feedback).
Constructor Details
#initialize(project_dir: Dir.pwd, repository: nil, template_manager: nil) ⇒ TemplateVersionManager
Returns a new instance of TemplateVersionManager.
33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/aidp/prompts/template_version_manager.rb', line 33 def initialize( project_dir: Dir.pwd, repository: nil, template_manager: nil ) @project_dir = project_dir @repository = repository || Database::Repositories::TemplateVersionRepository.new(project_dir: project_dir) # Pass self as version_manager to avoid circular dependency where # PromptTemplateManager creates its own TemplateVersionManager instance @template_manager = template_manager || PromptTemplateManager.new(project_dir: project_dir, version_manager: self) end |
Instance Attribute Details
#project_dir ⇒ Object (readonly)
Returns the value of attribute project_dir.
31 32 33 |
# File 'lib/aidp/prompts/template_version_manager.rb', line 31 def project_dir @project_dir end |
#repository ⇒ Object (readonly)
Returns the value of attribute repository.
31 32 33 |
# File 'lib/aidp/prompts/template_version_manager.rb', line 31 def repository @repository end |
#template_manager ⇒ Object (readonly)
Returns the value of attribute template_manager.
31 32 33 |
# File 'lib/aidp/prompts/template_version_manager.rb', line 31 def template_manager @template_manager end |
Instance Method Details
#activate_version(version_id:) ⇒ Hash
Activate a specific version
243 244 245 |
# File 'lib/aidp/prompts/template_version_manager.rb', line 243 def activate_version(version_id:) @repository.activate(id: version_id) end |
#active_version(template_id:) ⇒ Hash?
Get the currently active version
233 234 235 236 237 |
# File 'lib/aidp/prompts/template_version_manager.rb', line 233 def active_version(template_id:) return nil unless versionable?(template_id) @repository.active_version(template_id: template_id) end |
#best_version(template_id:) ⇒ Hash?
Get the best template version to use Prioritizes versions with higher positive votes
223 224 225 226 227 |
# File 'lib/aidp/prompts/template_version_manager.rb', line 223 def best_version(template_id:) return nil unless versionable?(template_id) @repository.best_version(template_id: template_id) end |
#create_evolved_version(template_id:, new_content:, parent_version_id:, metadata: {}) ⇒ Hash
Evolve a template by creating a new AGD-generated variant Called by TemplateEvolver after AI generates improved content
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'lib/aidp/prompts/template_version_manager.rb', line 191 def create_evolved_version(template_id:, new_content:, parent_version_id:, metadata: {}) return {success: false, error: "Template not versionable"} unless versionable?(template_id) Aidp.log_debug("template_version_manager", "creating_evolved_version", template_id: template_id, parent_version_id: parent_version_id) result = @repository.create( template_id: template_id, content: new_content, parent_version_id: parent_version_id, metadata: .merge(source: "agd_evolution") ) if result[:success] # Prune old versions @repository.prune_old_versions(template_id: template_id) Aidp.log_info("template_version_manager", "evolved_version_created", template_id: template_id, new_version_number: result[:version_number], parent_version_id: parent_version_id) end result end |
#initialize_versioning(template_id:) ⇒ Hash
Initialize versioning for a template by importing current content
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/aidp/prompts/template_version_manager.rb', line 73 def initialize_versioning(template_id:) return {success: false, error: "Template not versionable"} unless versionable?(template_id) return {success: true, already_versioned: true} if @repository.any?(template_id: template_id) Aidp.log_debug("template_version_manager", "initializing_versioning", template_id: template_id) # Load current template content from file (not versioned, since we're initializing) template_data = @template_manager.load_template(template_id, use_versioned: false) return {success: false, error: "Template not found"} unless template_data # Store as version 1 content = YAML.dump(template_data) result = @repository.create( template_id: template_id, content: content, metadata: {source: "initial_import"} ) if result[:success] Aidp.log_info("template_version_manager", "versioning_initialized", template_id: template_id, version_number: result[:version_number]) end result end |
#list_versions(template_id:, limit: 20) ⇒ Array<Hash>
List all versions for a template
252 253 254 255 256 |
# File 'lib/aidp/prompts/template_version_manager.rb', line 252 def list_versions(template_id:, limit: 20) return [] unless versionable?(template_id) @repository.list(template_id: template_id, limit: limit) end |
#record_negative_feedback(template_id:, suggestions: [], context: {}, evolve_on_negative: true) ⇒ Hash
Record negative feedback for active template version Triggers AGD-based evolution when evolve_on_negative is true
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/aidp/prompts/template_version_manager.rb', line 138 def record_negative_feedback(template_id:, suggestions: [], context: {}, evolve_on_negative: true) return {success: false, error: "Template not versionable"} unless versionable?(template_id) # Ensure versioning is initialized init_result = initialize_versioning(template_id: template_id) return init_result unless init_result[:success] || init_result[:already_versioned] active = @repository.active_version(template_id: template_id) return {success: false, error: "No active version"} unless active Aidp.log_debug("template_version_manager", "recording_negative_feedback", template_id: template_id, version_id: active[:id], suggestion_count: suggestions.size) # Record the negative vote result = @repository.record_negative_vote(id: active[:id]) return result unless result[:success] Aidp.log_info("template_version_manager", "negative_feedback_recorded", template_id: template_id, version_id: active[:id], total_negative: active[:negative_votes] + 1) # Store suggestions and context for later evolution evolution_result = { success: true, evolution_triggered: false, version_id: active[:id] } # Per issue #402: "Negative feedback (even a single vote) should trigger AGD" if evolve_on_negative evolution_result[:evolution_pending] = true evolution_result[:suggestions] = suggestions evolution_result[:context] = context Aidp.log_info("template_version_manager", "evolution_pending", template_id: template_id, version_id: active[:id]) end evolution_result end |
#record_positive_feedback(template_id:) ⇒ Hash
Record positive feedback for active template version
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/aidp/prompts/template_version_manager.rb', line 105 def record_positive_feedback(template_id:) return {success: false, error: "Template not versionable"} unless versionable?(template_id) # Ensure versioning is initialized init_result = initialize_versioning(template_id: template_id) return init_result unless init_result[:success] || init_result[:already_versioned] active = @repository.active_version(template_id: template_id) return {success: false, error: "No active version"} unless active Aidp.log_debug("template_version_manager", "recording_positive_feedback", template_id: template_id, version_id: active[:id]) result = @repository.record_positive_vote(id: active[:id]) if result[:success] Aidp.log_info("template_version_manager", "positive_feedback_recorded", template_id: template_id, version_id: active[:id]) end result end |
#render_versioned(template_id, **variables) ⇒ String?
Render a versioned template, using the active version if available
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 |
# File 'lib/aidp/prompts/template_version_manager.rb', line 299 def render_versioned(template_id, **variables) return nil unless versionable?(template_id) active = @repository.active_version(template_id: template_id) return nil unless active Aidp.log_debug("template_version_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 result = prompt_text.dup variables.each do |key, value| result.gsub!("{{#{key}}}", value.to_s) end result end |
#valid_template_id?(template_id) ⇒ Boolean
Validate template_id format (must be "category/name")
62 63 64 65 66 67 |
# File 'lib/aidp/prompts/template_version_manager.rb', line 62 def valid_template_id?(template_id) return false if template_id.nil? || template_id.empty? parts = template_id.split("/") parts.length == 2 && parts.all? { |p| !p.empty? } end |
#version_stats(template_id:) ⇒ Hash
Get version statistics for a template
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 |
# File 'lib/aidp/prompts/template_version_manager.rb', line 270 def version_stats(template_id:) return {error: "Template not versionable"} unless versionable?(template_id) versions = @repository.list(template_id: template_id, limit: 100) { template_id: template_id, total_versions: versions.size, active_version: versions.find { |v| v[:is_active] }&.dig(:version_number), total_positive_votes: versions.sum { |v| v[:positive_votes] }, total_negative_votes: versions.sum { |v| v[:negative_votes] }, best_version: @repository.best_version(template_id: template_id)&.dig(:version_number), oldest_version: versions.last&.dig(:created_at), newest_version: versions.first&.dig(:created_at) } end |
#versionable?(template_id) ⇒ Boolean
Check if a template is eligible for versioning
51 52 53 54 55 56 |
# File 'lib/aidp/prompts/template_version_manager.rb', line 51 def versionable?(template_id) return false unless valid_template_id?(template_id) category = template_id.split("/").first VERSIONED_CATEGORIES.include?(category) end |
#versioned_template_ids ⇒ Array<String>
Get all template IDs that have versioning enabled
290 291 292 |
# File 'lib/aidp/prompts/template_version_manager.rb', line 290 def versioned_template_ids @repository.template_ids end |
#versions_needing_evolution(template_id: nil) ⇒ Array<Hash>
Get versions that need evolution (have negative feedback)
262 263 264 |
# File 'lib/aidp/prompts/template_version_manager.rb', line 262 def versions_needing_evolution(template_id: nil) @repository.versions_needing_evolution(template_id: template_id) end |