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.
- checksums.yaml +4 -4
- data/README.md +14 -14
- data/lib/aidp/agent_harness_adapter.rb +309 -0
- data/lib/aidp/cli/issue_importer.rb +20 -6
- data/lib/aidp/cli/jobs_command.rb +1 -1
- data/lib/aidp/cli/models_command.rb +9 -16
- data/lib/aidp/cli/prompts_command.rb +253 -0
- data/lib/aidp/cli/security_command.rb +4 -10
- data/lib/aidp/cli/storage_command.rb +277 -0
- data/lib/aidp/cli/temporal_command.rb +460 -0
- data/lib/aidp/cli.rb +24 -2
- data/lib/aidp/config.rb +15 -0
- data/lib/aidp/config_paths.rb +12 -2
- data/lib/aidp/database/migrations.rb +114 -0
- data/lib/aidp/database/repositories/checkpoint_repository.rb +174 -0
- data/lib/aidp/database/repositories/deprecated_models_repository.rb +206 -0
- data/lib/aidp/database/repositories/evaluation_repository.rb +208 -0
- data/lib/aidp/database/repositories/harness_state_repository.rb +140 -0
- data/lib/aidp/database/repositories/job_repository.rb +278 -0
- data/lib/aidp/database/repositories/model_cache_repository.rb +194 -0
- data/lib/aidp/database/repositories/progress_repository.rb +210 -0
- data/lib/aidp/database/repositories/prompt_archive_repository.rb +195 -0
- data/lib/aidp/database/repositories/prompt_feedback_repository.rb +266 -0
- data/lib/aidp/database/repositories/provider_info_cache_repository.rb +208 -0
- data/lib/aidp/database/repositories/provider_metrics_repository.rb +245 -0
- data/lib/aidp/database/repositories/secrets_repository.rb +187 -0
- data/lib/aidp/database/repositories/task_repository.rb +231 -0
- data/lib/aidp/database/repositories/template_version_repository.rb +383 -0
- data/lib/aidp/database/repositories/watch_state_repository.rb +472 -0
- data/lib/aidp/database/repositories/workstream_repository.rb +310 -0
- data/lib/aidp/database/repositories/worktree_repository.rb +240 -0
- data/lib/aidp/database/repository.rb +166 -0
- data/lib/aidp/database/schema.rb +347 -0
- data/lib/aidp/database/storage_migrator.rb +693 -0
- data/lib/aidp/database.rb +139 -0
- data/lib/aidp/debug_mixin.rb +2 -2
- data/lib/aidp/execute/checkpoint.rb +26 -0
- data/lib/aidp/execute/checkpoint_display.rb +16 -0
- data/lib/aidp/execute/prompt_evaluator.rb +30 -57
- data/lib/aidp/execute/work_loop_runner.rb +294 -46
- data/lib/aidp/firewall/provider_requirements_collector.rb +7 -23
- data/lib/aidp/harness/agent_harness_provider_manager.rb +275 -0
- data/lib/aidp/harness/ai_decision_engine.rb +78 -296
- data/lib/aidp/harness/config_schema.rb +59 -0
- data/lib/aidp/harness/configuration.rb +229 -10
- data/lib/aidp/harness/deprecation_cache.rb +53 -11
- data/lib/aidp/harness/enhanced_runner.rb +1 -1
- data/lib/aidp/harness/error_handler.rb +11 -5
- data/lib/aidp/harness/provider_config.rb +36 -0
- data/lib/aidp/harness/provider_factory.rb +26 -24
- data/lib/aidp/harness/provider_info.rb +9 -5
- data/lib/aidp/harness/provider_manager.rb +2 -2
- data/lib/aidp/harness/provider_metrics.rb +103 -1
- data/lib/aidp/harness/rspec_command_optimizer.rb +166 -0
- data/lib/aidp/harness/ruby_llm_registry.rb +97 -8
- data/lib/aidp/harness/runner.rb +7 -2
- data/lib/aidp/harness/state_manager.rb +1 -1
- data/lib/aidp/harness/test_runner.rb +167 -11
- data/lib/aidp/harness/thinking_depth_manager.rb +564 -5
- data/lib/aidp/harness/usage_limit.rb +179 -0
- data/lib/aidp/harness/usage_limit_enforcer.rb +240 -0
- data/lib/aidp/harness/usage_limit_tracker.rb +308 -0
- data/lib/aidp/harness/usage_period.rb +234 -0
- data/lib/aidp/init/agent_instructions_generator.rb +317 -0
- data/lib/aidp/init/runner.rb +25 -4
- data/lib/aidp/interfaces/activity_monitor_interface.rb +234 -0
- data/lib/aidp/interfaces/binary_checker_interface.rb +232 -0
- data/lib/aidp/interfaces/command_executor_interface.rb +216 -0
- data/lib/aidp/interfaces/logger_interface.rb +150 -0
- data/lib/aidp/interfaces/ui_interface.rb +254 -0
- data/lib/aidp/logger.rb +25 -7
- data/lib/aidp/orchestration_adapter.rb +279 -0
- data/lib/aidp/pr_worktree_manager.rb +4 -15
- data/lib/aidp/prompts/feedback_collector.rb +198 -0
- data/lib/aidp/prompts/prompt_template_manager.rb +415 -0
- data/lib/aidp/prompts/template_evolver.rb +305 -0
- data/lib/aidp/prompts/template_version_manager.rb +325 -0
- data/lib/aidp/setup/in_memory_config_adapter.rb +257 -0
- data/lib/aidp/setup/in_memory_config_manager.rb +262 -0
- data/lib/aidp/setup/wizard.rb +616 -176
- data/lib/aidp/shell_executor.rb +58 -0
- data/lib/aidp/style_guide/selector.rb +56 -1
- data/lib/aidp/temporal/activities/analyze_issue_activity.rb +198 -0
- data/lib/aidp/temporal/activities/analyze_sub_task_activity.rb +163 -0
- data/lib/aidp/temporal/activities/base_activity.rb +78 -0
- data/lib/aidp/temporal/activities/create_plan_activity.rb +115 -0
- data/lib/aidp/temporal/activities/create_pr_activity.rb +193 -0
- data/lib/aidp/temporal/activities/create_prompt_activity.rb +118 -0
- data/lib/aidp/temporal/activities/diagnose_failure_activity.rb +228 -0
- data/lib/aidp/temporal/activities/prepare_next_iteration_activity.rb +147 -0
- data/lib/aidp/temporal/activities/record_checkpoint_activity.rb +81 -0
- data/lib/aidp/temporal/activities/run_agent_activity.rb +139 -0
- data/lib/aidp/temporal/activities/run_tests_activity.rb +114 -0
- data/lib/aidp/temporal/activities/run_work_loop_iteration_activity.rb +249 -0
- data/lib/aidp/temporal/configuration.rb +169 -0
- data/lib/aidp/temporal/connection.rb +93 -0
- data/lib/aidp/temporal/worker.rb +119 -0
- data/lib/aidp/temporal/workflow_client.rb +171 -0
- data/lib/aidp/temporal/workflows/base_workflow.rb +73 -0
- data/lib/aidp/temporal/workflows/issue_to_pr_workflow.rb +278 -0
- data/lib/aidp/temporal/workflows/sub_issue_workflow.rb +253 -0
- data/lib/aidp/temporal/workflows/work_loop_workflow.rb +321 -0
- data/lib/aidp/temporal.rb +120 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/base_processor.rb +35 -0
- data/lib/aidp/watch/build_processor.rb +132 -0
- data/lib/aidp/watch/github_state_extractor.rb +0 -8
- data/lib/aidp/watch/rebase_processor.rb +245 -0
- data/lib/aidp/watch/round_robin_scheduler.rb +150 -0
- data/lib/aidp/watch/runner.rb +366 -285
- data/lib/aidp/watch/state_store.rb +80 -0
- data/lib/aidp/watch/work_item.rb +96 -0
- data/lib/aidp/watch/worktree_cleanup_job.rb +226 -0
- data/lib/aidp/watch/worktree_reconciler.rb +643 -0
- data/lib/aidp/workflows/guided_agent.rb +1 -1
- data/lib/aidp/worktree.rb +62 -0
- data/templates/aidp.yml.example +70 -0
- metadata +117 -16
- data/lib/aidp/analyze/json_file_storage.rb +0 -292
- data/lib/aidp/execute/future_work_backlog.rb +0 -411
- data/lib/aidp/providers/adapter.rb +0 -239
- data/lib/aidp/providers/aider.rb +0 -264
- data/lib/aidp/providers/anthropic.rb +0 -614
- data/lib/aidp/providers/base.rb +0 -617
- data/lib/aidp/providers/capability_registry.rb +0 -204
- data/lib/aidp/providers/codex.rb +0 -267
- data/lib/aidp/providers/cursor.rb +0 -304
- data/lib/aidp/providers/error_taxonomy.rb +0 -195
- data/lib/aidp/providers/gemini.rb +0 -110
- data/lib/aidp/providers/github_copilot.rb +0 -227
- data/lib/aidp/providers/kilocode.rb +0 -157
- data/lib/aidp/providers/opencode.rb +0 -139
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../repository"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
module Database
|
|
7
|
+
module Repositories
|
|
8
|
+
# Repository for progress table
|
|
9
|
+
# Replaces progress/execute.yml and progress/analyze.yml
|
|
10
|
+
class ProgressRepository < Repository
|
|
11
|
+
VALID_MODES = %w[execute analyze].freeze
|
|
12
|
+
|
|
13
|
+
def initialize(project_dir: Dir.pwd)
|
|
14
|
+
super(project_dir: project_dir, table_name: "progress")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Get progress for a mode
|
|
18
|
+
#
|
|
19
|
+
# @param mode [String, Symbol] Mode (execute or analyze)
|
|
20
|
+
# @return [Hash] Progress data
|
|
21
|
+
def get(mode)
|
|
22
|
+
mode_str = mode.to_s
|
|
23
|
+
row = query_one(
|
|
24
|
+
"SELECT * FROM progress WHERE project_dir = ? AND mode = ?",
|
|
25
|
+
[project_dir, mode_str]
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
return empty_progress(mode_str) unless row
|
|
29
|
+
|
|
30
|
+
deserialize_progress(row)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Get completed steps
|
|
34
|
+
#
|
|
35
|
+
# @param mode [String, Symbol] Mode
|
|
36
|
+
# @return [Array<String>] Completed step names
|
|
37
|
+
def completed_steps(mode)
|
|
38
|
+
progress = get(mode)
|
|
39
|
+
progress[:steps_completed] || []
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Get current step
|
|
43
|
+
#
|
|
44
|
+
# @param mode [String, Symbol] Mode
|
|
45
|
+
# @return [String, nil] Current step name
|
|
46
|
+
def current_step(mode)
|
|
47
|
+
progress = get(mode)
|
|
48
|
+
progress[:current_step]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Check if step is completed
|
|
52
|
+
#
|
|
53
|
+
# @param mode [String, Symbol] Mode
|
|
54
|
+
# @param step_name [String] Step name
|
|
55
|
+
# @return [Boolean]
|
|
56
|
+
def step_completed?(mode, step_name)
|
|
57
|
+
completed_steps(mode).include?(step_name)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Mark step as completed
|
|
61
|
+
#
|
|
62
|
+
# @param mode [String, Symbol] Mode
|
|
63
|
+
# @param step_name [String] Step name
|
|
64
|
+
def mark_step_completed(mode, step_name)
|
|
65
|
+
mode_str = mode.to_s
|
|
66
|
+
progress = get(mode)
|
|
67
|
+
|
|
68
|
+
steps = progress[:steps_completed] || []
|
|
69
|
+
steps << step_name unless steps.include?(step_name)
|
|
70
|
+
|
|
71
|
+
now = current_timestamp
|
|
72
|
+
started_at = progress[:started_at] || now
|
|
73
|
+
|
|
74
|
+
upsert_progress(
|
|
75
|
+
mode: mode_str,
|
|
76
|
+
current_step: nil,
|
|
77
|
+
steps_completed: steps,
|
|
78
|
+
started_at: started_at,
|
|
79
|
+
updated_at: now
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
Aidp.log_debug("progress_repository", "step_completed",
|
|
83
|
+
mode: mode_str, step: step_name)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Mark step as in progress
|
|
87
|
+
#
|
|
88
|
+
# @param mode [String, Symbol] Mode
|
|
89
|
+
# @param step_name [String] Step name
|
|
90
|
+
def mark_step_in_progress(mode, step_name)
|
|
91
|
+
mode_str = mode.to_s
|
|
92
|
+
progress = get(mode)
|
|
93
|
+
|
|
94
|
+
now = current_timestamp
|
|
95
|
+
started_at = progress[:started_at] || now
|
|
96
|
+
|
|
97
|
+
upsert_progress(
|
|
98
|
+
mode: mode_str,
|
|
99
|
+
current_step: step_name,
|
|
100
|
+
steps_completed: progress[:steps_completed] || [],
|
|
101
|
+
started_at: started_at,
|
|
102
|
+
updated_at: now
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
Aidp.log_debug("progress_repository", "step_in_progress",
|
|
106
|
+
mode: mode_str, step: step_name)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Reset progress for a mode
|
|
110
|
+
#
|
|
111
|
+
# @param mode [String, Symbol] Mode
|
|
112
|
+
def reset(mode)
|
|
113
|
+
mode_str = mode.to_s
|
|
114
|
+
execute(
|
|
115
|
+
"DELETE FROM progress WHERE project_dir = ? AND mode = ?",
|
|
116
|
+
[project_dir, mode_str]
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
Aidp.log_debug("progress_repository", "reset", mode: mode_str)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Get started_at timestamp
|
|
123
|
+
#
|
|
124
|
+
# @param mode [String, Symbol] Mode
|
|
125
|
+
# @return [Time, nil] Started at time
|
|
126
|
+
def started_at(mode)
|
|
127
|
+
progress = get(mode)
|
|
128
|
+
return nil unless progress[:started_at]
|
|
129
|
+
|
|
130
|
+
Time.parse(progress[:started_at])
|
|
131
|
+
rescue ArgumentError
|
|
132
|
+
nil
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Upsert progress data (for migrations and direct updates)
|
|
136
|
+
#
|
|
137
|
+
# @param mode [String] Mode (execute or analyze)
|
|
138
|
+
# @param current_step [String, nil] Current step name
|
|
139
|
+
# @param steps_completed [Array<String>] Completed step names
|
|
140
|
+
# @param started_at [String] Started at timestamp
|
|
141
|
+
# @param updated_at [String] Updated at timestamp
|
|
142
|
+
def upsert_progress(mode:, current_step:, steps_completed:, started_at:, updated_at:)
|
|
143
|
+
existing = query_one(
|
|
144
|
+
"SELECT id FROM progress WHERE project_dir = ? AND mode = ?",
|
|
145
|
+
[project_dir, mode]
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
if existing
|
|
149
|
+
execute(
|
|
150
|
+
<<~SQL,
|
|
151
|
+
UPDATE progress SET
|
|
152
|
+
current_step = ?,
|
|
153
|
+
steps_completed = ?,
|
|
154
|
+
started_at = ?,
|
|
155
|
+
updated_at = ?
|
|
156
|
+
WHERE project_dir = ? AND mode = ?
|
|
157
|
+
SQL
|
|
158
|
+
[
|
|
159
|
+
current_step,
|
|
160
|
+
serialize_json(steps_completed),
|
|
161
|
+
started_at,
|
|
162
|
+
updated_at,
|
|
163
|
+
project_dir,
|
|
164
|
+
mode
|
|
165
|
+
]
|
|
166
|
+
)
|
|
167
|
+
else
|
|
168
|
+
execute(
|
|
169
|
+
insert_sql([
|
|
170
|
+
:project_dir, :mode, :current_step, :steps_completed,
|
|
171
|
+
:started_at, :updated_at
|
|
172
|
+
]),
|
|
173
|
+
[
|
|
174
|
+
project_dir,
|
|
175
|
+
mode,
|
|
176
|
+
current_step,
|
|
177
|
+
serialize_json(steps_completed),
|
|
178
|
+
started_at,
|
|
179
|
+
updated_at
|
|
180
|
+
]
|
|
181
|
+
)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
private
|
|
186
|
+
|
|
187
|
+
def empty_progress(mode)
|
|
188
|
+
{
|
|
189
|
+
mode: mode,
|
|
190
|
+
current_step: nil,
|
|
191
|
+
steps_completed: [],
|
|
192
|
+
started_at: nil,
|
|
193
|
+
updated_at: nil
|
|
194
|
+
}
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def deserialize_progress(row)
|
|
198
|
+
{
|
|
199
|
+
id: row["id"],
|
|
200
|
+
mode: row["mode"],
|
|
201
|
+
current_step: row["current_step"],
|
|
202
|
+
steps_completed: deserialize_json(row["steps_completed"]) || [],
|
|
203
|
+
started_at: row["started_at"],
|
|
204
|
+
updated_at: row["updated_at"]
|
|
205
|
+
}
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../repository"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
module Database
|
|
7
|
+
module Repositories
|
|
8
|
+
# Repository for prompt_archive table
|
|
9
|
+
# Replaces prompt_archive/*.md files
|
|
10
|
+
# Write-only audit trail of prompts
|
|
11
|
+
class PromptArchiveRepository < Repository
|
|
12
|
+
def initialize(project_dir: Dir.pwd)
|
|
13
|
+
super(project_dir: project_dir, table_name: "prompt_archive")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Archive a prompt
|
|
17
|
+
#
|
|
18
|
+
# @param step_name [String, nil] Step name
|
|
19
|
+
# @param content [String] Prompt content
|
|
20
|
+
# @return [Integer] Archive entry ID
|
|
21
|
+
def archive(step_name:, content:)
|
|
22
|
+
now = current_timestamp
|
|
23
|
+
|
|
24
|
+
execute(
|
|
25
|
+
insert_sql([:project_dir, :step_name, :content, :archived_at]),
|
|
26
|
+
[project_dir, step_name, content, now]
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
id = last_insert_row_id
|
|
30
|
+
|
|
31
|
+
Aidp.log_debug("prompt_archive_repository", "archived",
|
|
32
|
+
id: id, step: step_name, size: content.length)
|
|
33
|
+
|
|
34
|
+
id
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Get recent archived prompts
|
|
38
|
+
#
|
|
39
|
+
# @param limit [Integer] Maximum entries
|
|
40
|
+
# @param step_name [String, nil] Filter by step
|
|
41
|
+
# @return [Array<Hash>] Archive entries
|
|
42
|
+
def recent(limit: 50, step_name: nil)
|
|
43
|
+
rows = if step_name
|
|
44
|
+
query(
|
|
45
|
+
<<~SQL,
|
|
46
|
+
SELECT * FROM prompt_archive
|
|
47
|
+
WHERE project_dir = ? AND step_name = ?
|
|
48
|
+
ORDER BY archived_at DESC
|
|
49
|
+
LIMIT ?
|
|
50
|
+
SQL
|
|
51
|
+
[project_dir, step_name, limit]
|
|
52
|
+
)
|
|
53
|
+
else
|
|
54
|
+
query(
|
|
55
|
+
<<~SQL,
|
|
56
|
+
SELECT * FROM prompt_archive
|
|
57
|
+
WHERE project_dir = ?
|
|
58
|
+
ORDER BY archived_at DESC
|
|
59
|
+
LIMIT ?
|
|
60
|
+
SQL
|
|
61
|
+
[project_dir, limit]
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
rows.map { |row| deserialize_entry(row) }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Get archive entry by ID
|
|
69
|
+
#
|
|
70
|
+
# @param id [Integer] Entry ID
|
|
71
|
+
# @return [Hash, nil] Entry or nil
|
|
72
|
+
def find(id)
|
|
73
|
+
row = query_one(
|
|
74
|
+
"SELECT * FROM prompt_archive WHERE id = ? AND project_dir = ?",
|
|
75
|
+
[id, project_dir]
|
|
76
|
+
)
|
|
77
|
+
deserialize_entry(row)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Get latest archived prompt for a step
|
|
81
|
+
#
|
|
82
|
+
# @param step_name [String] Step name
|
|
83
|
+
# @return [Hash, nil] Latest entry or nil
|
|
84
|
+
def latest_for_step(step_name)
|
|
85
|
+
row = query_one(
|
|
86
|
+
<<~SQL,
|
|
87
|
+
SELECT * FROM prompt_archive
|
|
88
|
+
WHERE project_dir = ? AND step_name = ?
|
|
89
|
+
ORDER BY archived_at DESC, id DESC
|
|
90
|
+
LIMIT 1
|
|
91
|
+
SQL
|
|
92
|
+
[project_dir, step_name]
|
|
93
|
+
)
|
|
94
|
+
deserialize_entry(row)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Get archive stats
|
|
98
|
+
#
|
|
99
|
+
# @return [Hash] Statistics
|
|
100
|
+
def stats
|
|
101
|
+
total = query_value(
|
|
102
|
+
"SELECT COUNT(*) FROM prompt_archive WHERE project_dir = ?",
|
|
103
|
+
[project_dir]
|
|
104
|
+
) || 0
|
|
105
|
+
|
|
106
|
+
by_step_rows = query(
|
|
107
|
+
<<~SQL,
|
|
108
|
+
SELECT step_name, COUNT(*) as count
|
|
109
|
+
FROM prompt_archive
|
|
110
|
+
WHERE project_dir = ?
|
|
111
|
+
GROUP BY step_name
|
|
112
|
+
SQL
|
|
113
|
+
[project_dir]
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
by_step = by_step_rows.each_with_object({}) do |row, h|
|
|
117
|
+
h[row["step_name"] || "unknown"] = row["count"]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
first = query_one(
|
|
121
|
+
"SELECT archived_at FROM prompt_archive WHERE project_dir = ? ORDER BY archived_at ASC LIMIT 1",
|
|
122
|
+
[project_dir]
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
last = query_one(
|
|
126
|
+
"SELECT archived_at FROM prompt_archive WHERE project_dir = ? ORDER BY archived_at DESC LIMIT 1",
|
|
127
|
+
[project_dir]
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
{
|
|
131
|
+
total: total,
|
|
132
|
+
by_step: by_step,
|
|
133
|
+
first_archived_at: first&.dig("archived_at"),
|
|
134
|
+
last_archived_at: last&.dig("archived_at")
|
|
135
|
+
}
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Search archived prompts
|
|
139
|
+
#
|
|
140
|
+
# @param query_text [String] Search text
|
|
141
|
+
# @param limit [Integer] Maximum results
|
|
142
|
+
# @return [Array<Hash>] Matching entries
|
|
143
|
+
def search(query_text, limit: 20)
|
|
144
|
+
rows = query(
|
|
145
|
+
<<~SQL,
|
|
146
|
+
SELECT * FROM prompt_archive
|
|
147
|
+
WHERE project_dir = ? AND content LIKE ?
|
|
148
|
+
ORDER BY archived_at DESC
|
|
149
|
+
LIMIT ?
|
|
150
|
+
SQL
|
|
151
|
+
[project_dir, "%#{query_text}%", limit]
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
rows.map { |row| deserialize_entry(row) }
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Clear old archives (keep recent N days)
|
|
158
|
+
#
|
|
159
|
+
# @param days_to_keep [Integer] Days of history to retain
|
|
160
|
+
# @return [Integer] Number of entries deleted
|
|
161
|
+
def cleanup(days_to_keep: 30)
|
|
162
|
+
threshold = (Time.now - (days_to_keep * 24 * 60 * 60)).strftime("%Y-%m-%d %H:%M:%S")
|
|
163
|
+
|
|
164
|
+
count = query_value(
|
|
165
|
+
"SELECT COUNT(*) FROM prompt_archive WHERE project_dir = ? AND archived_at < ?",
|
|
166
|
+
[project_dir, threshold]
|
|
167
|
+
) || 0
|
|
168
|
+
|
|
169
|
+
execute(
|
|
170
|
+
"DELETE FROM prompt_archive WHERE project_dir = ? AND archived_at < ?",
|
|
171
|
+
[project_dir, threshold]
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
Aidp.log_debug("prompt_archive_repository", "cleanup",
|
|
175
|
+
deleted: count, threshold: threshold)
|
|
176
|
+
|
|
177
|
+
count
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
private
|
|
181
|
+
|
|
182
|
+
def deserialize_entry(row)
|
|
183
|
+
return nil unless row
|
|
184
|
+
|
|
185
|
+
{
|
|
186
|
+
id: row["id"],
|
|
187
|
+
step_name: row["step_name"],
|
|
188
|
+
content: row["content"],
|
|
189
|
+
archived_at: row["archived_at"]
|
|
190
|
+
}
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../repository"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
module Database
|
|
7
|
+
module Repositories
|
|
8
|
+
# Repository for prompt_feedback table
|
|
9
|
+
# Tracks prompt template effectiveness for AGD evolution
|
|
10
|
+
class PromptFeedbackRepository < Repository
|
|
11
|
+
VALID_OUTCOMES = %w[success failure abandoned timeout].freeze
|
|
12
|
+
VALID_REACTIONS = %w[positive negative neutral].freeze
|
|
13
|
+
|
|
14
|
+
def initialize(project_dir: Dir.pwd)
|
|
15
|
+
super(project_dir: project_dir, table_name: "prompt_feedback")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Record feedback for a prompt template
|
|
19
|
+
#
|
|
20
|
+
# @param record [Hash] Feedback data with keys:
|
|
21
|
+
# - template_id [String] Template identifier
|
|
22
|
+
# - outcome [String] success/failure/abandoned/timeout
|
|
23
|
+
# - iterations [Integer, nil] Number of iterations to completion
|
|
24
|
+
# - user_reaction [String, nil] positive/negative/neutral
|
|
25
|
+
# - suggestions [Array<String>, nil] Improvement suggestions
|
|
26
|
+
# - context [Hash, nil] Additional context
|
|
27
|
+
# @return [Hash] Result with :success and :id
|
|
28
|
+
def record(record)
|
|
29
|
+
execute(
|
|
30
|
+
insert_sql([
|
|
31
|
+
:project_dir, :template_id, :outcome, :iterations,
|
|
32
|
+
:user_reaction, :suggestions, :context, :aidp_version
|
|
33
|
+
]),
|
|
34
|
+
[
|
|
35
|
+
project_dir,
|
|
36
|
+
record[:template_id],
|
|
37
|
+
record[:outcome].to_s,
|
|
38
|
+
record[:iterations],
|
|
39
|
+
record[:user_reaction]&.to_s,
|
|
40
|
+
serialize_json(record[:suggestions]),
|
|
41
|
+
serialize_json(record[:context] || {}),
|
|
42
|
+
Aidp::VERSION
|
|
43
|
+
]
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
Aidp.log_debug("prompt_feedback_repository", "recorded",
|
|
47
|
+
template_id: record[:template_id], outcome: record[:outcome])
|
|
48
|
+
|
|
49
|
+
{success: true, id: last_insert_row_id}
|
|
50
|
+
rescue => e
|
|
51
|
+
Aidp.log_debug("prompt_feedback_repository", "record_failed",
|
|
52
|
+
template_id: record[:template_id], error: e.message)
|
|
53
|
+
{success: false, error: e.message}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Get summary statistics for a template
|
|
57
|
+
#
|
|
58
|
+
# @param template_id [String] Template identifier
|
|
59
|
+
# @return [Hash] Summary statistics
|
|
60
|
+
def summary(template_id:)
|
|
61
|
+
total = query_value(
|
|
62
|
+
"SELECT COUNT(*) FROM prompt_feedback WHERE project_dir = ? AND template_id = ?",
|
|
63
|
+
[project_dir, template_id]
|
|
64
|
+
) || 0
|
|
65
|
+
|
|
66
|
+
return empty_summary(template_id) if total.zero?
|
|
67
|
+
|
|
68
|
+
success_count = query_value(
|
|
69
|
+
"SELECT COUNT(*) FROM prompt_feedback WHERE project_dir = ? AND template_id = ? AND outcome = 'success'",
|
|
70
|
+
[project_dir, template_id]
|
|
71
|
+
) || 0
|
|
72
|
+
|
|
73
|
+
failure_count = query_value(
|
|
74
|
+
"SELECT COUNT(*) FROM prompt_feedback WHERE project_dir = ? AND template_id = ? AND outcome = 'failure'",
|
|
75
|
+
[project_dir, template_id]
|
|
76
|
+
) || 0
|
|
77
|
+
|
|
78
|
+
avg_iterations = query_value(
|
|
79
|
+
"SELECT AVG(iterations) FROM prompt_feedback WHERE project_dir = ? AND template_id = ? AND iterations IS NOT NULL",
|
|
80
|
+
[project_dir, template_id]
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
positive_reactions = query_value(
|
|
84
|
+
"SELECT COUNT(*) FROM prompt_feedback WHERE project_dir = ? AND template_id = ? AND user_reaction = 'positive'",
|
|
85
|
+
[project_dir, template_id]
|
|
86
|
+
) || 0
|
|
87
|
+
|
|
88
|
+
negative_reactions = query_value(
|
|
89
|
+
"SELECT COUNT(*) FROM prompt_feedback WHERE project_dir = ? AND template_id = ? AND user_reaction = 'negative'",
|
|
90
|
+
[project_dir, template_id]
|
|
91
|
+
) || 0
|
|
92
|
+
|
|
93
|
+
# Get suggestions
|
|
94
|
+
suggestion_rows = query(
|
|
95
|
+
"SELECT suggestions FROM prompt_feedback WHERE project_dir = ? AND template_id = ? AND suggestions IS NOT NULL",
|
|
96
|
+
[project_dir, template_id]
|
|
97
|
+
)
|
|
98
|
+
all_suggestions = suggestion_rows.flat_map { |r| deserialize_json(r["suggestions"]) || [] }.compact.uniq
|
|
99
|
+
|
|
100
|
+
first_row = query_one(
|
|
101
|
+
"SELECT created_at FROM prompt_feedback WHERE project_dir = ? AND template_id = ? ORDER BY created_at ASC LIMIT 1",
|
|
102
|
+
[project_dir, template_id]
|
|
103
|
+
)
|
|
104
|
+
last_row = query_one(
|
|
105
|
+
"SELECT created_at FROM prompt_feedback WHERE project_dir = ? AND template_id = ? ORDER BY created_at DESC LIMIT 1",
|
|
106
|
+
[project_dir, template_id]
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
{
|
|
110
|
+
template_id: template_id,
|
|
111
|
+
total_uses: total,
|
|
112
|
+
success_rate: (total > 0) ? (success_count.to_f / total * 100).round(1) : 0,
|
|
113
|
+
success_count: success_count,
|
|
114
|
+
failure_count: failure_count,
|
|
115
|
+
avg_iterations: avg_iterations&.round(1),
|
|
116
|
+
positive_reactions: positive_reactions,
|
|
117
|
+
negative_reactions: negative_reactions,
|
|
118
|
+
common_suggestions: all_suggestions.take(5),
|
|
119
|
+
first_use: first_row&.dig("created_at"),
|
|
120
|
+
last_use: last_row&.dig("created_at")
|
|
121
|
+
}
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# List feedback entries with filtering
|
|
125
|
+
#
|
|
126
|
+
# @param template_id [String, nil] Filter by template
|
|
127
|
+
# @param outcome [String, nil] Filter by outcome
|
|
128
|
+
# @param limit [Integer] Maximum entries
|
|
129
|
+
# @return [Array<Hash>] Feedback entries
|
|
130
|
+
def list(template_id: nil, outcome: nil, limit: 100)
|
|
131
|
+
conditions = ["project_dir = ?"]
|
|
132
|
+
params = [project_dir]
|
|
133
|
+
|
|
134
|
+
if template_id
|
|
135
|
+
conditions << "template_id = ?"
|
|
136
|
+
params << template_id
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
if outcome
|
|
140
|
+
conditions << "outcome = ?"
|
|
141
|
+
params << outcome.to_s
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
params << limit
|
|
145
|
+
|
|
146
|
+
rows = query(
|
|
147
|
+
<<~SQL,
|
|
148
|
+
SELECT * FROM prompt_feedback
|
|
149
|
+
WHERE #{conditions.join(" AND ")}
|
|
150
|
+
ORDER BY created_at DESC, id DESC
|
|
151
|
+
LIMIT ?
|
|
152
|
+
SQL
|
|
153
|
+
params
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
rows.map { |row| deserialize_feedback(row) }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Find templates that need improvement
|
|
160
|
+
#
|
|
161
|
+
# @param min_uses [Integer] Minimum uses to consider
|
|
162
|
+
# @param max_success_rate [Float] Success rate threshold
|
|
163
|
+
# @return [Array<Hash>] Templates needing improvement
|
|
164
|
+
def templates_needing_improvement(min_uses: 5, max_success_rate: 70.0)
|
|
165
|
+
# Use a single aggregated query instead of N+1 queries
|
|
166
|
+
rows = query(
|
|
167
|
+
<<~SQL,
|
|
168
|
+
SELECT
|
|
169
|
+
template_id,
|
|
170
|
+
COUNT(*) as total_uses,
|
|
171
|
+
SUM(CASE WHEN outcome = 'success' THEN 1 ELSE 0 END) as success_count,
|
|
172
|
+
SUM(CASE WHEN outcome = 'failure' THEN 1 ELSE 0 END) as failure_count,
|
|
173
|
+
AVG(CASE WHEN iterations IS NOT NULL THEN iterations END) as avg_iterations,
|
|
174
|
+
SUM(CASE WHEN user_reaction = 'positive' THEN 1 ELSE 0 END) as positive_reactions,
|
|
175
|
+
SUM(CASE WHEN user_reaction = 'negative' THEN 1 ELSE 0 END) as negative_reactions
|
|
176
|
+
FROM prompt_feedback
|
|
177
|
+
WHERE project_dir = ?
|
|
178
|
+
GROUP BY template_id
|
|
179
|
+
HAVING COUNT(*) >= ?
|
|
180
|
+
SQL
|
|
181
|
+
[project_dir, min_uses]
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
rows.filter_map do |row|
|
|
185
|
+
total = row["total_uses"]
|
|
186
|
+
success_count = row["success_count"]
|
|
187
|
+
success_rate = (total > 0) ? (success_count.to_f / total * 100).round(1) : 0
|
|
188
|
+
|
|
189
|
+
next if success_rate > max_success_rate
|
|
190
|
+
|
|
191
|
+
{
|
|
192
|
+
template_id: row["template_id"],
|
|
193
|
+
total_uses: total,
|
|
194
|
+
success_rate: success_rate,
|
|
195
|
+
success_count: success_count,
|
|
196
|
+
failure_count: row["failure_count"],
|
|
197
|
+
avg_iterations: row["avg_iterations"]&.round(1),
|
|
198
|
+
positive_reactions: row["positive_reactions"],
|
|
199
|
+
negative_reactions: row["negative_reactions"]
|
|
200
|
+
}
|
|
201
|
+
end.sort_by { |s| s[:success_rate] }
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Clear all feedback
|
|
205
|
+
#
|
|
206
|
+
# @return [Hash] Result with count
|
|
207
|
+
def clear
|
|
208
|
+
count = query_value(
|
|
209
|
+
"SELECT COUNT(*) FROM prompt_feedback WHERE project_dir = ?",
|
|
210
|
+
[project_dir]
|
|
211
|
+
) || 0
|
|
212
|
+
|
|
213
|
+
execute("DELETE FROM prompt_feedback WHERE project_dir = ?", [project_dir])
|
|
214
|
+
|
|
215
|
+
Aidp.log_debug("prompt_feedback_repository", "cleared", count: count)
|
|
216
|
+
{success: true, count: count}
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Check if any feedback exists
|
|
220
|
+
#
|
|
221
|
+
# @return [Boolean]
|
|
222
|
+
def any?
|
|
223
|
+
count = query_value(
|
|
224
|
+
"SELECT COUNT(*) FROM prompt_feedback WHERE project_dir = ?",
|
|
225
|
+
[project_dir]
|
|
226
|
+
) || 0
|
|
227
|
+
count.positive?
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
private
|
|
231
|
+
|
|
232
|
+
def deserialize_feedback(row)
|
|
233
|
+
return nil unless row
|
|
234
|
+
|
|
235
|
+
{
|
|
236
|
+
id: row["id"],
|
|
237
|
+
template_id: row["template_id"],
|
|
238
|
+
outcome: row["outcome"],
|
|
239
|
+
iterations: row["iterations"],
|
|
240
|
+
user_reaction: row["user_reaction"],
|
|
241
|
+
suggestions: deserialize_json(row["suggestions"]),
|
|
242
|
+
context: deserialize_json(row["context"]) || {},
|
|
243
|
+
aidp_version: row["aidp_version"],
|
|
244
|
+
created_at: row["created_at"]
|
|
245
|
+
}
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def empty_summary(template_id)
|
|
249
|
+
{
|
|
250
|
+
template_id: template_id,
|
|
251
|
+
total_uses: 0,
|
|
252
|
+
success_rate: 0,
|
|
253
|
+
success_count: 0,
|
|
254
|
+
failure_count: 0,
|
|
255
|
+
avg_iterations: nil,
|
|
256
|
+
positive_reactions: 0,
|
|
257
|
+
negative_reactions: 0,
|
|
258
|
+
common_suggestions: [],
|
|
259
|
+
first_use: nil,
|
|
260
|
+
last_use: nil
|
|
261
|
+
}
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|