Class: Aidp::Database::Repositories::TemplateVersionRepository

Inherits:
Aidp::Database::Repository show all
Defined in:
lib/aidp/database/repositories/template_version_repository.rb

Overview

Repository for template_versions table Manages versioned prompt templates with feedback-based selection

Per issue #402:

  • Store template versions in SQLite
  • Track positive/negative votes per version
  • Retain last 5 versions, ensuring at least 2 positive-feedback versions
  • Select best version based on positive vote count

Constant Summary collapse

MIN_VERSIONS =

Minimum versions to retain per template

5
MIN_POSITIVE_VERSIONS =

Minimum positive-feedback versions to retain

2

Instance Attribute Summary

Attributes inherited from Aidp::Database::Repository

#project_dir, #table_name

Instance Method Summary collapse

Constructor Details

#initialize(project_dir: Dir.pwd) ⇒ TemplateVersionRepository

Returns a new instance of TemplateVersionRepository.



24
25
26
# File 'lib/aidp/database/repositories/template_version_repository.rb', line 24

def initialize(project_dir: Dir.pwd)
  super(project_dir: project_dir, table_name: "template_versions")
end

Instance Method Details

#activate(id:) ⇒ Hash

Activate a specific version

Parameters:

  • id (Integer)

    Version ID to activate

Returns:

  • (Hash)

    Result with :success



184
185
186
187
188
189
190
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
# File 'lib/aidp/database/repositories/template_version_repository.rb', line 184

def activate(id:)
  version = find(id: id)
  return {success: false, error: "Version not found"} unless version

  Aidp.log_debug("template_version_repository", "activating_version",
    id: id,
    template_id: version[:template_id])

  transaction do
    # Deactivate all versions for this template
    execute(
      "UPDATE template_versions SET is_active = 0 WHERE project_dir = ? AND template_id = ?",
      [project_dir, version[:template_id]]
    )

    # Activate the specified version
    execute(
      "UPDATE template_versions SET is_active = 1 WHERE id = ? AND project_dir = ?",
      [id, project_dir]
    )
  end

  Aidp.log_info("template_version_repository", "version_activated",
    id: id,
    template_id: version[:template_id])

  {success: true}
rescue => e
  Aidp.log_error("template_version_repository", "activation_failed",
    id: id, error: e.message)
  {success: false, error: e.message}
end

#active_version(template_id:) ⇒ Hash?

Get the active version for a template

Parameters:

  • template_id (String)

    Template identifier

Returns:

  • (Hash, nil)

    Active version or nil



97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/aidp/database/repositories/template_version_repository.rb', line 97

def active_version(template_id:)
  row = query_one(
    <<~SQL,
      SELECT * FROM template_versions
      WHERE project_dir = ? AND template_id = ? AND is_active = 1
      LIMIT 1
    SQL
    [project_dir, template_id]
  )

  deserialize_version(row)
end

#any?(template_id:) ⇒ Boolean

Check if any versions exist for a template

Parameters:

  • template_id (String)

    Template identifier

Returns:

  • (Boolean)


322
323
324
# File 'lib/aidp/database/repositories/template_version_repository.rb', line 322

def any?(template_id:)
  count(template_id: template_id).positive?
end

#best_version(template_id:) ⇒ Hash?

Get the best version for a template based on positive votes Prioritizes higher positive vote counts

Parameters:

  • template_id (String)

    Template identifier

Returns:

  • (Hash, nil)

    Best version or nil



222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/aidp/database/repositories/template_version_repository.rb', line 222

def best_version(template_id:)
  row = query_one(
    <<~SQL,
      SELECT * FROM template_versions
      WHERE project_dir = ? AND template_id = ?
      ORDER BY positive_votes DESC, version_number DESC
      LIMIT 1
    SQL
    [project_dir, template_id]
  )

  deserialize_version(row)
end

#count(template_id:) ⇒ Integer

Count versions for a template

Parameters:

  • template_id (String)

    Template identifier

Returns:

  • (Integer)

    Version count



311
312
313
314
315
316
# File 'lib/aidp/database/repositories/template_version_repository.rb', line 311

def count(template_id:)
  query_value(
    "SELECT COUNT(*) FROM template_versions WHERE project_dir = ? AND template_id = ?",
    [project_dir, template_id]
  ) || 0
end

#create(template_id:, content:, parent_version_id: nil, metadata: nil) ⇒ Hash

Create a new template version

Parameters:

  • template_id (String)

    Template identifier (e.g., "work_loop/decide_whats_next")

  • content (String)

    Full YAML content of the template

  • parent_version_id (Integer, nil) (defaults to: nil)

    ID of parent version (for AGD-generated variants)

  • metadata (Hash, nil) (defaults to: nil)

    Additional metadata

Returns:

  • (Hash)

    Result with :success, :id, :version_number



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/aidp/database/repositories/template_version_repository.rb', line 35

def create(template_id:, content:, parent_version_id: nil, metadata: nil)
  # Validate content is present
  if content.nil? || content.to_s.strip.empty?
    Aidp.log_error("template_version_repository", "invalid_content",
      template_id: template_id,
      error: "Content cannot be nil or empty")
    return {success: false, error: "Content cannot be nil or empty"}
  end

  Aidp.log_debug("template_version_repository", "creating_version",
    template_id: template_id,
    parent_version_id: parent_version_id)

  transaction do
    # Get next version number for this template
    next_version = next_version_number(template_id)

    # Deactivate all existing versions for this template
    execute(
      "UPDATE template_versions SET is_active = 0 WHERE project_dir = ? AND template_id = ?",
      [project_dir, template_id]
    )

    # Insert new version as active
    execute(
      insert_sql([
        :project_dir, :template_id, :version_number, :content,
        :parent_version_id, :is_active, :positive_votes, :negative_votes, :metadata
      ]),
      [
        project_dir,
        template_id,
        next_version,
        content,
        parent_version_id,
        1, # is_active
        0, # positive_votes
        0, # negative_votes
        serialize_json()
      ]
    )

    id = last_insert_row_id

    Aidp.log_info("template_version_repository", "version_created",
      template_id: template_id,
      version_number: next_version,
      id: id)

    {success: true, id: id, version_number: next_version}
  end
rescue => e
  Aidp.log_error("template_version_repository", "create_failed",
    template_id: template_id,
    error: e.message)
  {success: false, error: e.message}
end

#find(id:) ⇒ Hash?

Get a specific version by ID

Parameters:

  • id (Integer)

    Version ID

Returns:

  • (Hash, nil)

    Version or nil



114
115
116
117
118
119
120
121
# File 'lib/aidp/database/repositories/template_version_repository.rb', line 114

def find(id:)
  row = query_one(
    "SELECT * FROM template_versions WHERE id = ? AND project_dir = ?",
    [id, project_dir]
  )

  deserialize_version(row)
end

#list(template_id:, limit: 20) ⇒ Array<Hash>

Get all versions for a template

Parameters:

  • template_id (String)

    Template identifier

  • limit (Integer) (defaults to: 20)

    Maximum versions to return

Returns:

  • (Array<Hash>)

    Versions sorted by version_number descending



128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/aidp/database/repositories/template_version_repository.rb', line 128

def list(template_id:, limit: 20)
  rows = query(
    <<~SQL,
      SELECT * FROM template_versions
      WHERE project_dir = ? AND template_id = ?
      ORDER BY version_number DESC
      LIMIT ?
    SQL
    [project_dir, template_id, limit]
  )

  rows.map { |row| deserialize_version(row) }.compact
end

#prune_old_versions(template_id:) ⇒ Hash

Prune old versions, keeping:

  • At least MIN_VERSIONS (5) versions
  • At least MIN_POSITIVE_VERSIONS (2) positive-feedback versions

Parameters:

  • template_id (String)

    Template identifier

Returns:

  • (Hash)

    Result with :success, :pruned_count



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/aidp/database/repositories/template_version_repository.rb', line 270

def prune_old_versions(template_id:)
  Aidp.log_debug("template_version_repository", "pruning_versions",
    template_id: template_id)

  # Capture active version reference before list/delete operations.
  # This ensures we never prune the currently active version, even if
  # activation changes during pruning (defensive programming).
  active = active_version(template_id: template_id)

  versions = list(template_id: template_id, limit: 100)
  return {success: true, pruned_count: 0} if versions.size <= MIN_VERSIONS

  # Identify versions to keep
  versions_to_keep = identify_versions_to_keep(versions)
  versions_to_prune = versions.reject { |v| versions_to_keep.include?(v[:id]) }

  # Never prune the active version
  versions_to_prune.reject! { |v| v[:id] == active&.dig(:id) }

  pruned_count = 0
  versions_to_prune.each do |version|
    execute("DELETE FROM template_versions WHERE id = ?", [version[:id]])
    pruned_count += 1
  end

  Aidp.log_info("template_version_repository", "versions_pruned",
    template_id: template_id,
    pruned_count: pruned_count,
    remaining_count: versions.size - pruned_count)

  {success: true, pruned_count: pruned_count}
rescue => e
  Aidp.log_error("template_version_repository", "prune_failed",
    template_id: template_id, error: e.message)
  {success: false, error: e.message}
end

#record_negative_vote(id:) ⇒ Hash

Record a negative vote for a version

Parameters:

  • id (Integer)

    Version ID

Returns:

  • (Hash)

    Result with :success



165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/aidp/database/repositories/template_version_repository.rb', line 165

def record_negative_vote(id:)
  Aidp.log_debug("template_version_repository", "recording_negative_vote", id: id)

  execute(
    "UPDATE template_versions SET negative_votes = negative_votes + 1 WHERE id = ? AND project_dir = ?",
    [id, project_dir]
  )

  {success: true}
rescue => e
  Aidp.log_error("template_version_repository", "negative_vote_failed",
    id: id, error: e.message)
  {success: false, error: e.message}
end

#record_positive_vote(id:) ⇒ Hash

Record a positive vote for a version

Parameters:

  • id (Integer)

    Version ID

Returns:

  • (Hash)

    Result with :success



146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/aidp/database/repositories/template_version_repository.rb', line 146

def record_positive_vote(id:)
  Aidp.log_debug("template_version_repository", "recording_positive_vote", id: id)

  execute(
    "UPDATE template_versions SET positive_votes = positive_votes + 1 WHERE id = ? AND project_dir = ?",
    [id, project_dir]
  )

  {success: true}
rescue => e
  Aidp.log_error("template_version_repository", "positive_vote_failed",
    id: id, error: e.message)
  {success: false, error: e.message}
end

#template_idsArray<String>

Get all unique template IDs with versions

Returns:

  • (Array<String>)

    Template IDs



329
330
331
332
333
334
335
336
# File 'lib/aidp/database/repositories/template_version_repository.rb', line 329

def template_ids
  rows = query(
    "SELECT DISTINCT template_id FROM template_versions WHERE project_dir = ? ORDER BY template_id",
    [project_dir]
  )

  rows.map { |r| r["template_id"] }
end

#versions_needing_evolution(template_id: nil) ⇒ Array<Hash>

Get versions needing evolution (have negative votes)

Parameters:

  • template_id (String, nil) (defaults to: nil)

    Filter by template (nil for all)

Returns:

  • (Array<Hash>)

    Versions with negative votes



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/aidp/database/repositories/template_version_repository.rb', line 240

def versions_needing_evolution(template_id: nil)
  rows = if template_id
    query(
      <<~SQL,
        SELECT * FROM template_versions
        WHERE project_dir = ? AND template_id = ? AND negative_votes > 0
        ORDER BY negative_votes DESC
      SQL
      [project_dir, template_id]
    )
  else
    query(
      <<~SQL,
        SELECT * FROM template_versions
        WHERE project_dir = ? AND negative_votes > 0
        ORDER BY negative_votes DESC
      SQL
      [project_dir]
    )
  end

  rows.map { |row| deserialize_version(row) }.compact
end