aidp 0.34.1 → 0.39.3

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.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -14
  3. data/lib/aidp/agent_harness_adapter.rb +309 -0
  4. data/lib/aidp/cli/issue_importer.rb +20 -6
  5. data/lib/aidp/cli/jobs_command.rb +1 -1
  6. data/lib/aidp/cli/models_command.rb +9 -16
  7. data/lib/aidp/cli/prompts_command.rb +253 -0
  8. data/lib/aidp/cli/security_command.rb +4 -10
  9. data/lib/aidp/cli/storage_command.rb +277 -0
  10. data/lib/aidp/cli/temporal_command.rb +460 -0
  11. data/lib/aidp/cli.rb +24 -2
  12. data/lib/aidp/config.rb +15 -0
  13. data/lib/aidp/config_paths.rb +12 -2
  14. data/lib/aidp/database/migrations.rb +114 -0
  15. data/lib/aidp/database/repositories/checkpoint_repository.rb +174 -0
  16. data/lib/aidp/database/repositories/deprecated_models_repository.rb +206 -0
  17. data/lib/aidp/database/repositories/evaluation_repository.rb +208 -0
  18. data/lib/aidp/database/repositories/harness_state_repository.rb +140 -0
  19. data/lib/aidp/database/repositories/job_repository.rb +278 -0
  20. data/lib/aidp/database/repositories/model_cache_repository.rb +194 -0
  21. data/lib/aidp/database/repositories/progress_repository.rb +210 -0
  22. data/lib/aidp/database/repositories/prompt_archive_repository.rb +195 -0
  23. data/lib/aidp/database/repositories/prompt_feedback_repository.rb +266 -0
  24. data/lib/aidp/database/repositories/provider_info_cache_repository.rb +208 -0
  25. data/lib/aidp/database/repositories/provider_metrics_repository.rb +245 -0
  26. data/lib/aidp/database/repositories/secrets_repository.rb +187 -0
  27. data/lib/aidp/database/repositories/task_repository.rb +231 -0
  28. data/lib/aidp/database/repositories/template_version_repository.rb +383 -0
  29. data/lib/aidp/database/repositories/watch_state_repository.rb +472 -0
  30. data/lib/aidp/database/repositories/workstream_repository.rb +310 -0
  31. data/lib/aidp/database/repositories/worktree_repository.rb +240 -0
  32. data/lib/aidp/database/repository.rb +166 -0
  33. data/lib/aidp/database/schema.rb +347 -0
  34. data/lib/aidp/database/storage_migrator.rb +693 -0
  35. data/lib/aidp/database.rb +139 -0
  36. data/lib/aidp/debug_mixin.rb +2 -2
  37. data/lib/aidp/execute/checkpoint.rb +26 -0
  38. data/lib/aidp/execute/checkpoint_display.rb +16 -0
  39. data/lib/aidp/execute/prompt_evaluator.rb +30 -57
  40. data/lib/aidp/execute/work_loop_runner.rb +294 -46
  41. data/lib/aidp/firewall/provider_requirements_collector.rb +7 -23
  42. data/lib/aidp/harness/agent_harness_provider_manager.rb +275 -0
  43. data/lib/aidp/harness/ai_decision_engine.rb +78 -296
  44. data/lib/aidp/harness/config_schema.rb +59 -0
  45. data/lib/aidp/harness/configuration.rb +229 -10
  46. data/lib/aidp/harness/deprecation_cache.rb +53 -11
  47. data/lib/aidp/harness/enhanced_runner.rb +1 -1
  48. data/lib/aidp/harness/error_handler.rb +11 -5
  49. data/lib/aidp/harness/provider_config.rb +36 -0
  50. data/lib/aidp/harness/provider_factory.rb +26 -24
  51. data/lib/aidp/harness/provider_info.rb +9 -5
  52. data/lib/aidp/harness/provider_manager.rb +2 -2
  53. data/lib/aidp/harness/provider_metrics.rb +103 -1
  54. data/lib/aidp/harness/rspec_command_optimizer.rb +166 -0
  55. data/lib/aidp/harness/ruby_llm_registry.rb +97 -8
  56. data/lib/aidp/harness/runner.rb +7 -2
  57. data/lib/aidp/harness/state_manager.rb +1 -1
  58. data/lib/aidp/harness/test_runner.rb +167 -11
  59. data/lib/aidp/harness/thinking_depth_manager.rb +564 -5
  60. data/lib/aidp/harness/usage_limit.rb +179 -0
  61. data/lib/aidp/harness/usage_limit_enforcer.rb +240 -0
  62. data/lib/aidp/harness/usage_limit_tracker.rb +308 -0
  63. data/lib/aidp/harness/usage_period.rb +234 -0
  64. data/lib/aidp/init/agent_instructions_generator.rb +317 -0
  65. data/lib/aidp/init/runner.rb +25 -4
  66. data/lib/aidp/interfaces/activity_monitor_interface.rb +234 -0
  67. data/lib/aidp/interfaces/binary_checker_interface.rb +232 -0
  68. data/lib/aidp/interfaces/command_executor_interface.rb +216 -0
  69. data/lib/aidp/interfaces/logger_interface.rb +150 -0
  70. data/lib/aidp/interfaces/ui_interface.rb +254 -0
  71. data/lib/aidp/logger.rb +25 -7
  72. data/lib/aidp/orchestration_adapter.rb +279 -0
  73. data/lib/aidp/pr_worktree_manager.rb +4 -15
  74. data/lib/aidp/prompts/feedback_collector.rb +198 -0
  75. data/lib/aidp/prompts/prompt_template_manager.rb +415 -0
  76. data/lib/aidp/prompts/template_evolver.rb +305 -0
  77. data/lib/aidp/prompts/template_version_manager.rb +325 -0
  78. data/lib/aidp/setup/in_memory_config_adapter.rb +257 -0
  79. data/lib/aidp/setup/in_memory_config_manager.rb +262 -0
  80. data/lib/aidp/setup/wizard.rb +616 -176
  81. data/lib/aidp/shell_executor.rb +58 -0
  82. data/lib/aidp/style_guide/selector.rb +56 -1
  83. data/lib/aidp/temporal/activities/analyze_issue_activity.rb +198 -0
  84. data/lib/aidp/temporal/activities/analyze_sub_task_activity.rb +163 -0
  85. data/lib/aidp/temporal/activities/base_activity.rb +78 -0
  86. data/lib/aidp/temporal/activities/create_plan_activity.rb +115 -0
  87. data/lib/aidp/temporal/activities/create_pr_activity.rb +193 -0
  88. data/lib/aidp/temporal/activities/create_prompt_activity.rb +118 -0
  89. data/lib/aidp/temporal/activities/diagnose_failure_activity.rb +228 -0
  90. data/lib/aidp/temporal/activities/prepare_next_iteration_activity.rb +147 -0
  91. data/lib/aidp/temporal/activities/record_checkpoint_activity.rb +81 -0
  92. data/lib/aidp/temporal/activities/run_agent_activity.rb +139 -0
  93. data/lib/aidp/temporal/activities/run_tests_activity.rb +114 -0
  94. data/lib/aidp/temporal/activities/run_work_loop_iteration_activity.rb +249 -0
  95. data/lib/aidp/temporal/configuration.rb +169 -0
  96. data/lib/aidp/temporal/connection.rb +93 -0
  97. data/lib/aidp/temporal/worker.rb +119 -0
  98. data/lib/aidp/temporal/workflow_client.rb +171 -0
  99. data/lib/aidp/temporal/workflows/base_workflow.rb +73 -0
  100. data/lib/aidp/temporal/workflows/issue_to_pr_workflow.rb +278 -0
  101. data/lib/aidp/temporal/workflows/sub_issue_workflow.rb +253 -0
  102. data/lib/aidp/temporal/workflows/work_loop_workflow.rb +321 -0
  103. data/lib/aidp/temporal.rb +120 -0
  104. data/lib/aidp/version.rb +1 -1
  105. data/lib/aidp/watch/base_processor.rb +35 -0
  106. data/lib/aidp/watch/build_processor.rb +132 -0
  107. data/lib/aidp/watch/github_state_extractor.rb +0 -8
  108. data/lib/aidp/watch/rebase_processor.rb +245 -0
  109. data/lib/aidp/watch/round_robin_scheduler.rb +150 -0
  110. data/lib/aidp/watch/runner.rb +366 -285
  111. data/lib/aidp/watch/state_store.rb +80 -0
  112. data/lib/aidp/watch/work_item.rb +96 -0
  113. data/lib/aidp/watch/worktree_cleanup_job.rb +226 -0
  114. data/lib/aidp/watch/worktree_reconciler.rb +643 -0
  115. data/lib/aidp/workflows/guided_agent.rb +1 -1
  116. data/lib/aidp/worktree.rb +62 -0
  117. data/templates/aidp.yml.example +70 -0
  118. metadata +117 -16
  119. data/lib/aidp/analyze/json_file_storage.rb +0 -292
  120. data/lib/aidp/execute/future_work_backlog.rb +0 -411
  121. data/lib/aidp/providers/adapter.rb +0 -239
  122. data/lib/aidp/providers/aider.rb +0 -264
  123. data/lib/aidp/providers/anthropic.rb +0 -614
  124. data/lib/aidp/providers/base.rb +0 -617
  125. data/lib/aidp/providers/capability_registry.rb +0 -204
  126. data/lib/aidp/providers/codex.rb +0 -267
  127. data/lib/aidp/providers/cursor.rb +0 -304
  128. data/lib/aidp/providers/error_taxonomy.rb +0 -195
  129. data/lib/aidp/providers/gemini.rb +0 -110
  130. data/lib/aidp/providers/github_copilot.rb +0 -227
  131. data/lib/aidp/providers/kilocode.rb +0 -157
  132. data/lib/aidp/providers/opencode.rb +0 -139
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../repository"
4
+
5
+ module Aidp
6
+ module Database
7
+ module Repositories
8
+ # Repository for checkpoint and checkpoint_history tables
9
+ # Replaces checkpoint.yml and checkpoint_history.jsonl
10
+ class CheckpointRepository < Repository
11
+ def initialize(project_dir: Dir.pwd)
12
+ super(project_dir: project_dir, table_name: "checkpoints")
13
+ end
14
+
15
+ # Save or update current checkpoint
16
+ #
17
+ # @param step_name [String]
18
+ # @param iteration [Integer]
19
+ # @param status [String]
20
+ # @param run_loop_started_at [String, nil]
21
+ # @param metrics [Hash]
22
+ # @return [Integer] Checkpoint ID
23
+ def save_checkpoint(step_name:, iteration: nil, status: nil, run_loop_started_at: nil, metrics: {})
24
+ data = {
25
+ step_name: step_name,
26
+ iteration: iteration,
27
+ status: status,
28
+ run_loop_started_at: run_loop_started_at,
29
+ metrics: metrics
30
+ }
31
+ existing = current_checkpoint
32
+ now = current_timestamp
33
+
34
+ if existing
35
+ existing_id = existing[:id]
36
+ execute(
37
+ <<~SQL,
38
+ UPDATE checkpoints SET
39
+ step_name = ?,
40
+ step_index = ?,
41
+ status = ?,
42
+ run_loop_started_at = ?,
43
+ metadata = ?,
44
+ updated_at = ?
45
+ WHERE id = ?
46
+ SQL
47
+ [
48
+ data[:step_name],
49
+ data[:iteration],
50
+ data[:status],
51
+ data[:run_loop_started_at],
52
+ serialize_json(data[:metrics]),
53
+ now,
54
+ existing_id
55
+ ]
56
+ )
57
+ Aidp.log_debug("checkpoint_repository", "updated", id: existing_id)
58
+ existing_id
59
+ else
60
+ execute(
61
+ insert_sql([
62
+ :project_dir, :step_name, :step_index, :status,
63
+ :run_loop_started_at, :metadata, :created_at, :updated_at
64
+ ]),
65
+ [
66
+ project_dir,
67
+ data[:step_name],
68
+ data[:iteration],
69
+ data[:status],
70
+ data[:run_loop_started_at],
71
+ serialize_json(data[:metrics]),
72
+ now,
73
+ now
74
+ ]
75
+ )
76
+ id = last_insert_row_id
77
+ Aidp.log_debug("checkpoint_repository", "created", id: id)
78
+ id
79
+ end
80
+ end
81
+
82
+ # Get current checkpoint for project
83
+ #
84
+ # @return [Hash, nil] Current checkpoint or nil
85
+ def current_checkpoint
86
+ row = query_one(
87
+ "SELECT * FROM checkpoints WHERE project_dir = ? ORDER BY updated_at DESC LIMIT 1",
88
+ [project_dir]
89
+ )
90
+ return nil unless row
91
+
92
+ deserialize_checkpoint(row)
93
+ end
94
+
95
+ # Append checkpoint to history
96
+ #
97
+ # @param step_name [String]
98
+ # @param iteration [Integer]
99
+ # @param status [String]
100
+ # @param timestamp [String, nil]
101
+ # @param metrics [Hash]
102
+ def append_history(step_name:, iteration: nil, status: nil, timestamp: nil, metrics: {})
103
+ execute(
104
+ <<~SQL,
105
+ INSERT INTO checkpoint_history (project_dir, step_name, step_index, status, timestamp, metadata)
106
+ VALUES (?, ?, ?, ?, ?, ?)
107
+ SQL
108
+ [
109
+ project_dir,
110
+ step_name,
111
+ iteration,
112
+ status,
113
+ timestamp || current_timestamp,
114
+ serialize_json(metrics)
115
+ ]
116
+ )
117
+ Aidp.log_debug("checkpoint_repository", "history_appended", step: step_name)
118
+ end
119
+
120
+ # Get checkpoint history
121
+ #
122
+ # @param limit [Integer] Maximum entries to return
123
+ # @return [Array<Hash>] Checkpoint history entries
124
+ def history(limit: 100)
125
+ rows = query(
126
+ <<~SQL,
127
+ SELECT * FROM checkpoint_history
128
+ WHERE project_dir = ?
129
+ ORDER BY timestamp DESC, id DESC
130
+ LIMIT ?
131
+ SQL
132
+ [project_dir, limit]
133
+ )
134
+
135
+ rows.map { |row| deserialize_history_entry(row) }.reverse
136
+ end
137
+
138
+ # Clear all checkpoint data for project
139
+ def clear
140
+ transaction do
141
+ execute("DELETE FROM checkpoints WHERE project_dir = ?", [project_dir])
142
+ execute("DELETE FROM checkpoint_history WHERE project_dir = ?", [project_dir])
143
+ end
144
+ Aidp.log_debug("checkpoint_repository", "cleared", project_dir: project_dir)
145
+ end
146
+
147
+ private
148
+
149
+ def deserialize_checkpoint(row)
150
+ {
151
+ id: row["id"],
152
+ step_name: row["step_name"],
153
+ iteration: row["step_index"],
154
+ status: row["status"],
155
+ run_loop_started_at: row["run_loop_started_at"],
156
+ metrics: deserialize_json(row["metadata"]) || {},
157
+ created_at: row["created_at"],
158
+ updated_at: row["updated_at"]
159
+ }
160
+ end
161
+
162
+ def deserialize_history_entry(row)
163
+ {
164
+ step_name: row["step_name"],
165
+ iteration: row["step_index"],
166
+ status: row["status"],
167
+ timestamp: row["timestamp"],
168
+ metrics: deserialize_json(row["metadata"]) || {}
169
+ }
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../repository"
4
+
5
+ module Aidp
6
+ module Database
7
+ module Repositories
8
+ # Repository for deprecated models cache
9
+ # Replaces: .aidp/deprecated_models.json
10
+ #
11
+ # Tracks models that have been deprecated by providers at runtime
12
+ class DeprecatedModelsRepository < Repository
13
+ def initialize(project_dir: Dir.pwd)
14
+ super(project_dir: project_dir, table_name: "deprecated_models")
15
+ end
16
+
17
+ # Add a deprecated model to the cache
18
+ #
19
+ # @param provider_name [String] Provider name (e.g., "anthropic")
20
+ # @param model_name [String] Deprecated model ID
21
+ # @param replacement [String, nil] Replacement model ID
22
+ # @param reason [String, nil] Deprecation reason
23
+ # @return [Boolean] Success status
24
+ def add(provider_name:, model_name:, replacement: nil, reason: nil)
25
+ Aidp.log_debug("deprecated_models_repo", "add",
26
+ provider: provider_name, model: model_name, replacement: replacement)
27
+
28
+ execute(<<~SQL, [project_dir, provider_name, model_name, replacement, reason, current_timestamp])
29
+ INSERT INTO #{table_name}
30
+ (project_dir, provider_name, model_name, replacement, reason, detected_at)
31
+ VALUES (?, ?, ?, ?, ?, ?)
32
+ ON CONFLICT(project_dir, provider_name, model_name) DO UPDATE SET
33
+ replacement = excluded.replacement,
34
+ reason = excluded.reason,
35
+ detected_at = excluded.detected_at
36
+ SQL
37
+
38
+ Aidp.log_info("deprecated_models_repo", "added_deprecated_model",
39
+ provider: provider_name, model: model_name, replacement: replacement)
40
+ true
41
+ rescue => e
42
+ Aidp.log_error("deprecated_models_repo", "add_failed",
43
+ provider: provider_name, model: model_name, error: e.message)
44
+ false
45
+ end
46
+
47
+ # Check if a model is deprecated
48
+ #
49
+ # @param provider_name [String] Provider name
50
+ # @param model_name [String] Model ID to check
51
+ # @return [Boolean]
52
+ def deprecated?(provider_name:, model_name:)
53
+ result = query_value(<<~SQL, [project_dir, provider_name, model_name])
54
+ SELECT 1 FROM #{table_name}
55
+ WHERE project_dir = ? AND provider_name = ? AND model_name = ?
56
+ SQL
57
+
58
+ !result.nil?
59
+ end
60
+
61
+ # Get replacement model for a deprecated model
62
+ #
63
+ # @param provider_name [String] Provider name
64
+ # @param model_name [String] Deprecated model ID
65
+ # @return [String, nil] Replacement model ID or nil
66
+ def replacement_for(provider_name:, model_name:)
67
+ query_value(<<~SQL, [project_dir, provider_name, model_name])
68
+ SELECT replacement FROM #{table_name}
69
+ WHERE project_dir = ? AND provider_name = ? AND model_name = ?
70
+ SQL
71
+ end
72
+
73
+ # Get all deprecated models for a provider
74
+ #
75
+ # @param provider_name [String] Provider name
76
+ # @return [Array<String>] List of deprecated model IDs
77
+ def deprecated_models(provider_name:)
78
+ rows = query(<<~SQL, [project_dir, provider_name])
79
+ SELECT model_name FROM #{table_name}
80
+ WHERE project_dir = ? AND provider_name = ?
81
+ ORDER BY model_name
82
+ SQL
83
+
84
+ rows.map { |row| row["model_name"] }
85
+ end
86
+
87
+ # Get full deprecation info for a model
88
+ #
89
+ # @param provider_name [String] Provider name
90
+ # @param model_name [String] Model ID
91
+ # @return [Hash, nil] Deprecation metadata or nil
92
+ def info(provider_name:, model_name:)
93
+ row = query_one(<<~SQL, [project_dir, provider_name, model_name])
94
+ SELECT replacement, reason, detected_at
95
+ FROM #{table_name}
96
+ WHERE project_dir = ? AND provider_name = ? AND model_name = ?
97
+ SQL
98
+
99
+ return nil unless row
100
+
101
+ {
102
+ replacement: row["replacement"],
103
+ reason: row["reason"],
104
+ detected_at: row["detected_at"]
105
+ }
106
+ end
107
+
108
+ # Remove a model from the deprecated cache
109
+ #
110
+ # @param provider_name [String] Provider name
111
+ # @param model_name [String] Model ID to remove
112
+ def remove(provider_name:, model_name:)
113
+ Aidp.log_debug("deprecated_models_repo", "remove",
114
+ provider: provider_name, model: model_name)
115
+
116
+ execute(<<~SQL, [project_dir, provider_name, model_name])
117
+ DELETE FROM #{table_name}
118
+ WHERE project_dir = ? AND provider_name = ? AND model_name = ?
119
+ SQL
120
+
121
+ Aidp.log_info("deprecated_models_repo", "removed_deprecated_model",
122
+ provider: provider_name, model: model_name)
123
+ end
124
+
125
+ # Clear all deprecations for project
126
+ def clear!
127
+ Aidp.log_debug("deprecated_models_repo", "clear")
128
+ delete_by_project
129
+ Aidp.log_info("deprecated_models_repo", "cleared_all")
130
+ end
131
+
132
+ # Get cache statistics
133
+ #
134
+ # @return [Hash] Statistics
135
+ def stats
136
+ providers_rows = query(<<~SQL, [project_dir])
137
+ SELECT DISTINCT provider_name FROM #{table_name}
138
+ WHERE project_dir = ?
139
+ ORDER BY provider_name
140
+ SQL
141
+
142
+ providers = providers_rows.map { |row| row["provider_name"] }
143
+
144
+ by_provider = {}
145
+ providers.each do |provider|
146
+ count = query_value(<<~SQL, [project_dir, provider])
147
+ SELECT COUNT(*) FROM #{table_name}
148
+ WHERE project_dir = ? AND provider_name = ?
149
+ SQL
150
+ by_provider[provider] = count || 0
151
+ end
152
+
153
+ total = by_provider.values.sum
154
+
155
+ {
156
+ providers: providers,
157
+ total_deprecated: total,
158
+ by_provider: by_provider
159
+ }
160
+ end
161
+
162
+ # List all deprecated models with full info
163
+ #
164
+ # @return [Array<Hash>] All deprecated models
165
+ def list_all
166
+ rows = query(<<~SQL, [project_dir])
167
+ SELECT provider_name, model_name, replacement, reason, detected_at
168
+ FROM #{table_name}
169
+ WHERE project_dir = ?
170
+ ORDER BY provider_name, model_name
171
+ SQL
172
+
173
+ rows.map do |row|
174
+ {
175
+ provider: row["provider_name"],
176
+ model: row["model_name"],
177
+ replacement: row["replacement"],
178
+ reason: row["reason"],
179
+ detected_at: row["detected_at"]
180
+ }
181
+ end
182
+ end
183
+
184
+ # Find deprecated models with replacements
185
+ #
186
+ # @return [Array<Hash>] Models with replacement info
187
+ def with_replacements
188
+ rows = query(<<~SQL, [project_dir])
189
+ SELECT provider_name, model_name, replacement
190
+ FROM #{table_name}
191
+ WHERE project_dir = ? AND replacement IS NOT NULL
192
+ ORDER BY provider_name, model_name
193
+ SQL
194
+
195
+ rows.map do |row|
196
+ {
197
+ provider: row["provider_name"],
198
+ model: row["model_name"],
199
+ replacement: row["replacement"]
200
+ }
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../repository"
4
+
5
+ module Aidp
6
+ module Database
7
+ module Repositories
8
+ # Repository for evaluations table
9
+ # Replaces evaluations/*.json and index.json
10
+ class EvaluationRepository < Repository
11
+ VALID_RATINGS = %w[good neutral bad].freeze
12
+
13
+ def initialize(project_dir: Dir.pwd)
14
+ super(project_dir: project_dir, table_name: "evaluations")
15
+ end
16
+
17
+ # Store a new evaluation
18
+ #
19
+ # @param record [Hash] Evaluation data with keys:
20
+ # - id [String] Evaluation ID
21
+ # - rating [String] good/neutral/bad
22
+ # - target_type [String] Type of item being evaluated
23
+ # - target_id [String] ID of item being evaluated
24
+ # - feedback [String, nil] User feedback text
25
+ # - context [Hash, nil] Additional context
26
+ # @return [Hash] Result with :success and :id
27
+ def store(record)
28
+ execute(
29
+ insert_sql([
30
+ :id, :project_dir, :rating, :target_type, :target_id,
31
+ :comment, :context
32
+ ]),
33
+ [
34
+ record[:id],
35
+ project_dir,
36
+ record[:rating],
37
+ record[:target_type],
38
+ record[:target_id],
39
+ record[:feedback],
40
+ serialize_json(record[:context] || {})
41
+ ]
42
+ )
43
+
44
+ Aidp.log_debug("evaluation_repository", "stored",
45
+ id: record[:id], rating: record[:rating])
46
+
47
+ {success: true, id: record[:id]}
48
+ rescue => e
49
+ Aidp.log_debug("evaluation_repository", "store_failed",
50
+ id: record[:id], error: e.message)
51
+ {success: false, error: e.message, id: record[:id]}
52
+ end
53
+
54
+ # Load an evaluation by ID
55
+ #
56
+ # @param id [String] Evaluation ID
57
+ # @return [Hash, nil] Evaluation or nil
58
+ def load(id)
59
+ row = query_one(
60
+ "SELECT * FROM evaluations WHERE id = ? AND project_dir = ?",
61
+ [id, project_dir]
62
+ )
63
+ deserialize_evaluation(row)
64
+ end
65
+
66
+ # List evaluations with optional filtering
67
+ #
68
+ # @param limit [Integer] Maximum records
69
+ # @param rating [String, nil] Filter by rating
70
+ # @param target_type [String, nil] Filter by target type
71
+ # @return [Array<Hash>] Evaluations
72
+ def list(limit: 50, rating: nil, target_type: nil)
73
+ conditions = ["project_dir = ?"]
74
+ params = [project_dir]
75
+
76
+ if rating
77
+ conditions << "rating = ?"
78
+ params << rating
79
+ end
80
+
81
+ if target_type
82
+ conditions << "target_type = ?"
83
+ params << target_type
84
+ end
85
+
86
+ params << limit
87
+
88
+ rows = query(
89
+ <<~SQL,
90
+ SELECT * FROM evaluations
91
+ WHERE #{conditions.join(" AND ")}
92
+ ORDER BY created_at DESC
93
+ LIMIT ?
94
+ SQL
95
+ params
96
+ )
97
+
98
+ rows.map { |row| deserialize_evaluation(row) }
99
+ end
100
+
101
+ # Get statistics
102
+ #
103
+ # @return [Hash] Statistics
104
+ def stats
105
+ total = query_value(
106
+ "SELECT COUNT(*) FROM evaluations WHERE project_dir = ?",
107
+ [project_dir]
108
+ ) || 0
109
+
110
+ by_rating = {}
111
+ %w[good neutral bad].each do |r|
112
+ by_rating[r.to_sym] = query_value(
113
+ "SELECT COUNT(*) FROM evaluations WHERE project_dir = ? AND rating = ?",
114
+ [project_dir, r]
115
+ ) || 0
116
+ end
117
+
118
+ by_type_rows = query(
119
+ <<~SQL,
120
+ SELECT target_type, COUNT(*) as count
121
+ FROM evaluations
122
+ WHERE project_dir = ?
123
+ GROUP BY target_type
124
+ SQL
125
+ [project_dir]
126
+ )
127
+ by_target_type = by_type_rows.each_with_object({}) do |row, h|
128
+ h[row["target_type"]] = row["count"]
129
+ end
130
+
131
+ first_row = query_one(
132
+ "SELECT created_at FROM evaluations WHERE project_dir = ? ORDER BY created_at ASC LIMIT 1",
133
+ [project_dir]
134
+ )
135
+ last_row = query_one(
136
+ "SELECT created_at FROM evaluations WHERE project_dir = ? ORDER BY created_at DESC LIMIT 1",
137
+ [project_dir]
138
+ )
139
+
140
+ {
141
+ total: total,
142
+ by_rating: by_rating,
143
+ by_target_type: by_target_type,
144
+ first_evaluation: first_row&.dig("created_at"),
145
+ last_evaluation: last_row&.dig("created_at")
146
+ }
147
+ end
148
+
149
+ # Delete an evaluation
150
+ #
151
+ # @param id [String] Evaluation ID
152
+ # @return [Hash] Result
153
+ def delete(id)
154
+ execute(
155
+ "DELETE FROM evaluations WHERE id = ? AND project_dir = ?",
156
+ [id, project_dir]
157
+ )
158
+ Aidp.log_debug("evaluation_repository", "deleted", id: id)
159
+ {success: true, id: id}
160
+ end
161
+
162
+ # Clear all evaluations
163
+ #
164
+ # @return [Hash] Result with count
165
+ def clear
166
+ count = query_value(
167
+ "SELECT COUNT(*) FROM evaluations WHERE project_dir = ?",
168
+ [project_dir]
169
+ ) || 0
170
+
171
+ execute("DELETE FROM evaluations WHERE project_dir = ?", [project_dir])
172
+
173
+ Aidp.log_debug("evaluation_repository", "cleared", count: count)
174
+ {success: true, count: count}
175
+ end
176
+
177
+ # Check if any evaluations exist
178
+ #
179
+ # @return [Boolean]
180
+ def any?
181
+ count = query_value(
182
+ "SELECT COUNT(*) FROM evaluations WHERE project_dir = ?",
183
+ [project_dir]
184
+ ) || 0
185
+ count.positive?
186
+ end
187
+
188
+ private
189
+
190
+ def deserialize_evaluation(row)
191
+ return nil unless row
192
+
193
+ context = deserialize_json(row["context"]) || {}
194
+
195
+ {
196
+ id: row["id"],
197
+ rating: row["rating"],
198
+ target_type: row["target_type"],
199
+ target_id: row["target_id"],
200
+ feedback: row["comment"],
201
+ context: context,
202
+ created_at: row["created_at"]
203
+ }
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end