aidp 0.47.1 → 0.48.0
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/lib/aidp/cli/temporal_command.rb +132 -3
- data/lib/aidp/database/repositories/experience_artifact_repository.rb +40 -0
- data/lib/aidp/database/repositories/experience_evaluation_repository.rb +55 -0
- data/lib/aidp/database/repositories/experience_run_repository.rb +143 -0
- data/lib/aidp/database/repositories/experience_task_repository.rb +62 -0
- data/lib/aidp/database/repositories/strategy_repository.rb +59 -0
- data/lib/aidp/database/schema.rb +95 -1
- data/lib/aidp/database.rb +38 -0
- data/lib/aidp/strategy_execution/cli_protocol.rb +119 -0
- data/lib/aidp/strategy_execution/experience_store.rb +155 -0
- data/lib/aidp/strategy_execution/strategy_loader.rb +26 -0
- data/lib/aidp/strategy_execution/strategy_spec.rb +127 -0
- data/lib/aidp/temporal/activities/execute_cli_command_activity.rb +51 -0
- data/lib/aidp/temporal/activities/manage_experience_store_activity.rb +56 -0
- data/lib/aidp/temporal/workflows/strategy_branch_workflow.rb +294 -0
- data/lib/aidp/temporal/workflows/strategy_execution_workflow.rb +294 -0
- data/lib/aidp/temporal.rb +8 -0
- data/lib/aidp/version.rb +1 -1
- metadata +14 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 497afaf23ef14fc5ebced21f0ca90a090bcc66f642002fc289d3757dc9a136f8
|
|
4
|
+
data.tar.gz: f2a8e664cb040b4869a5b2b1ccf15fabe9524baed622bf6b9a04ce57e0d3c864
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 69b44c42f6c45c7d7239674c2f06f3326b1437c477bea43f98dfcf9fdfab3ed1a9811c14fd20ec49e7af0af0efe798c0cfe7e74443f43a39de263f30e62cb745
|
|
7
|
+
data.tar.gz: 5be71e4d983121166264108f9711245d7f26129a26e318a6a0ce9f38b3a5aaabba30544e818c41a6606671554e8a6264adba4abdd622b0c49e8d0c0e90051591
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
require "tty-prompt"
|
|
4
4
|
require "tty-table"
|
|
5
5
|
require "pastel"
|
|
6
|
+
require_relative "../strategy_execution/strategy_loader"
|
|
7
|
+
require_relative "../strategy_execution/experience_store"
|
|
6
8
|
require_relative "terminal_io"
|
|
7
9
|
|
|
8
10
|
module Aidp
|
|
@@ -30,6 +32,10 @@ module Aidp
|
|
|
30
32
|
case subcommand
|
|
31
33
|
when "start", nil
|
|
32
34
|
start_workflow(args)
|
|
35
|
+
when "replay"
|
|
36
|
+
replay_workflow(args)
|
|
37
|
+
when "benchmark"
|
|
38
|
+
benchmark_workflows(args)
|
|
33
39
|
when "list"
|
|
34
40
|
list_workflows(args)
|
|
35
41
|
when "status"
|
|
@@ -61,6 +67,9 @@ module Aidp
|
|
|
61
67
|
display_message("")
|
|
62
68
|
display_message("Temporal Workflow Commands:", type: :info)
|
|
63
69
|
display_message(" aidp temporal start [issue_number] - Start issue-to-PR workflow", type: :info)
|
|
70
|
+
display_message(" aidp temporal start strategy <strategy.yml> [task.json] - Start strategy workflow", type: :info)
|
|
71
|
+
display_message(" aidp temporal replay <run_id> <strategy.yml> - Replay a task with a new strategy", type: :info)
|
|
72
|
+
display_message(" aidp temporal benchmark <strategy.yml> <run_id> [run_id...] - Replay many runs", type: :info)
|
|
64
73
|
display_message(" aidp temporal list - List active workflows", type: :info)
|
|
65
74
|
display_message(" aidp temporal status <workflow_id> - Show workflow status", type: :info)
|
|
66
75
|
display_message(" aidp temporal signal <workflow_id> <signal> - Send signal to workflow", type: :info)
|
|
@@ -80,9 +89,11 @@ module Aidp
|
|
|
80
89
|
start_issue_to_pr_workflow(issue_number)
|
|
81
90
|
when "workloop", "work_loop"
|
|
82
91
|
start_work_loop_workflow(args)
|
|
92
|
+
when "strategy"
|
|
93
|
+
start_strategy_workflow(args)
|
|
83
94
|
else
|
|
84
95
|
display_message("Unknown workflow type: #{workflow_type}", type: :error)
|
|
85
|
-
display_message("Available: issue, workloop", type: :info)
|
|
96
|
+
display_message("Available: issue, workloop, strategy", type: :info)
|
|
86
97
|
end
|
|
87
98
|
end
|
|
88
99
|
|
|
@@ -90,6 +101,7 @@ module Aidp
|
|
|
90
101
|
@prompt.select("Select workflow type:") do |menu|
|
|
91
102
|
menu.choice "Issue to PR (full pipeline)", "issue"
|
|
92
103
|
menu.choice "Work Loop (fix-forward iteration)", "workloop"
|
|
104
|
+
menu.choice "Strategy Execution", "strategy"
|
|
93
105
|
end
|
|
94
106
|
end
|
|
95
107
|
|
|
@@ -144,6 +156,92 @@ module Aidp
|
|
|
144
156
|
display_message("Monitor with: aidp temporal status #{handle.id}", type: :info)
|
|
145
157
|
end
|
|
146
158
|
|
|
159
|
+
def start_strategy_workflow(args)
|
|
160
|
+
strategy_path = args.shift
|
|
161
|
+
task_path = args.shift
|
|
162
|
+
|
|
163
|
+
unless strategy_path
|
|
164
|
+
display_message("Usage: aidp temporal start strategy <strategy.yml> [task.json]", type: :error)
|
|
165
|
+
return
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
strategy = load_strategy(strategy_path)
|
|
169
|
+
task = task_path ? load_task(task_path) : prompt_for_task
|
|
170
|
+
|
|
171
|
+
handle = Aidp::Temporal.start_workflow(
|
|
172
|
+
Aidp::Temporal::Workflows::StrategyExecutionWorkflow,
|
|
173
|
+
{
|
|
174
|
+
project_dir: @project_dir,
|
|
175
|
+
strategy: strategy.to_h,
|
|
176
|
+
task: task
|
|
177
|
+
},
|
|
178
|
+
project_dir: @project_dir
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
display_message("Started strategy workflow #{strategy.name}", type: :success)
|
|
182
|
+
display_message(" Workflow ID: #{handle.id}", type: :info)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def replay_workflow(args)
|
|
186
|
+
original_run_id = args.shift
|
|
187
|
+
strategy_path = args.shift
|
|
188
|
+
|
|
189
|
+
unless original_run_id && strategy_path
|
|
190
|
+
display_message("Usage: aidp temporal replay <run_id> <strategy.yml>", type: :error)
|
|
191
|
+
return
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
bundle = experience_store.replay_bundle(original_run_id)
|
|
195
|
+
unless bundle
|
|
196
|
+
display_message("Replay source run not found: #{original_run_id}", type: :error)
|
|
197
|
+
return
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
strategy = load_strategy(strategy_path)
|
|
201
|
+
replay_task = build_replay_task(bundle[:task], original_run_id)
|
|
202
|
+
|
|
203
|
+
handle = Aidp::Temporal.start_workflow(
|
|
204
|
+
Aidp::Temporal::Workflows::StrategyExecutionWorkflow,
|
|
205
|
+
{
|
|
206
|
+
project_dir: @project_dir,
|
|
207
|
+
strategy: strategy.to_h,
|
|
208
|
+
task: replay_task
|
|
209
|
+
},
|
|
210
|
+
project_dir: @project_dir
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
display_message("Replay started from run #{original_run_id}", type: :success)
|
|
214
|
+
display_message(" Workflow ID: #{handle.id}", type: :info)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def benchmark_workflows(args)
|
|
218
|
+
strategy_path = args.shift
|
|
219
|
+
run_ids = args
|
|
220
|
+
|
|
221
|
+
unless strategy_path && run_ids.any?
|
|
222
|
+
display_message("Usage: aidp temporal benchmark <strategy.yml> <run_id> [run_id...]", type: :error)
|
|
223
|
+
return
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
strategy = load_strategy(strategy_path)
|
|
227
|
+
started = run_ids.filter_map do |run_id|
|
|
228
|
+
bundle = experience_store.replay_bundle(run_id)
|
|
229
|
+
next unless bundle
|
|
230
|
+
|
|
231
|
+
Aidp::Temporal.start_workflow(
|
|
232
|
+
Aidp::Temporal::Workflows::StrategyExecutionWorkflow,
|
|
233
|
+
{
|
|
234
|
+
project_dir: @project_dir,
|
|
235
|
+
strategy: strategy.to_h,
|
|
236
|
+
task: build_replay_task(bundle[:task], run_id)
|
|
237
|
+
},
|
|
238
|
+
project_dir: @project_dir
|
|
239
|
+
).id
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
display_message("Started #{started.length} benchmark replay workflow(s)", type: :success)
|
|
243
|
+
end
|
|
244
|
+
|
|
147
245
|
def list_workflows(args)
|
|
148
246
|
require_relative "../temporal"
|
|
149
247
|
|
|
@@ -327,7 +425,9 @@ module Aidp
|
|
|
327
425
|
worker.register_workflows(
|
|
328
426
|
Aidp::Temporal::Workflows::IssueToPrWorkflow,
|
|
329
427
|
Aidp::Temporal::Workflows::WorkLoopWorkflow,
|
|
330
|
-
Aidp::Temporal::Workflows::SubIssueWorkflow
|
|
428
|
+
Aidp::Temporal::Workflows::SubIssueWorkflow,
|
|
429
|
+
Aidp::Temporal::Workflows::StrategyBranchWorkflow,
|
|
430
|
+
Aidp::Temporal::Workflows::StrategyExecutionWorkflow
|
|
331
431
|
)
|
|
332
432
|
|
|
333
433
|
# Register all activities
|
|
@@ -342,7 +442,9 @@ module Aidp
|
|
|
342
442
|
Aidp::Temporal::Activities::RecordCheckpointActivity.new,
|
|
343
443
|
Aidp::Temporal::Activities::CreatePrActivity.new,
|
|
344
444
|
Aidp::Temporal::Activities::RunWorkLoopIterationActivity.new,
|
|
345
|
-
Aidp::Temporal::Activities::AnalyzeSubTaskActivity.new
|
|
445
|
+
Aidp::Temporal::Activities::AnalyzeSubTaskActivity.new,
|
|
446
|
+
Aidp::Temporal::Activities::ExecuteCliCommandActivity.new,
|
|
447
|
+
Aidp::Temporal::Activities::ManageExperienceStoreActivity.new
|
|
346
448
|
)
|
|
347
449
|
|
|
348
450
|
display_message(@pastel.green("Worker started"), type: :success)
|
|
@@ -420,6 +522,33 @@ module Aidp
|
|
|
420
522
|
display_message(" 3. Start workflow: aidp temporal start issue <number>", type: :info)
|
|
421
523
|
end
|
|
422
524
|
|
|
525
|
+
def load_strategy(strategy_path)
|
|
526
|
+
StrategyExecution::StrategyLoader.new(project_dir: @project_dir).load_file(strategy_path)
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
def build_replay_task(bundle_task, source_run_id)
|
|
530
|
+
bundle_task.except(:id, :source_run_id).merge(source_run_id: source_run_id)
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
def load_task(task_path)
|
|
534
|
+
JSON.parse(File.read(expand_path(task_path)), symbolize_names: true)
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
def prompt_for_task
|
|
538
|
+
{
|
|
539
|
+
title: @prompt.ask("Task title:"),
|
|
540
|
+
description: @prompt.ask("Task description:")
|
|
541
|
+
}
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
def experience_store
|
|
545
|
+
@experience_store ||= StrategyExecution::ExperienceStore.new(project_dir: @project_dir)
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
def expand_path(path)
|
|
549
|
+
File.expand_path(path, @project_dir)
|
|
550
|
+
end
|
|
551
|
+
|
|
423
552
|
def format_status(status)
|
|
424
553
|
case status.to_s
|
|
425
554
|
when "running"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../repository"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
module Database
|
|
7
|
+
module Repositories
|
|
8
|
+
class ExperienceArtifactRepository < Repository
|
|
9
|
+
def initialize(project_dir: Dir.pwd)
|
|
10
|
+
super(project_dir: project_dir, table_name: "experience_artifacts")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def record(run_id:, role:, path:, metadata: {})
|
|
14
|
+
execute(
|
|
15
|
+
insert_sql([:project_dir, :run_id, :role, :path, :metadata]),
|
|
16
|
+
[project_dir, run_id, role, path, serialize_json(metadata || {})]
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
Aidp.log_debug("experience_artifact_repository", "recorded",
|
|
20
|
+
run_id: run_id, role: role, path: path)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def list_for_run(run_id)
|
|
24
|
+
rows = query(
|
|
25
|
+
"SELECT * FROM experience_artifacts WHERE run_id = ? AND project_dir = ? ORDER BY id ASC",
|
|
26
|
+
[run_id, project_dir]
|
|
27
|
+
)
|
|
28
|
+
rows.map do |row|
|
|
29
|
+
{
|
|
30
|
+
role: row["role"],
|
|
31
|
+
path: row["path"],
|
|
32
|
+
metadata: deserialize_json(row["metadata"]) || {},
|
|
33
|
+
created_at: row["created_at"]
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../repository"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
module Database
|
|
7
|
+
module Repositories
|
|
8
|
+
class ExperienceEvaluationRepository < Repository
|
|
9
|
+
def initialize(project_dir: Dir.pwd)
|
|
10
|
+
super(project_dir: project_dir, table_name: "experience_evaluations")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def record(run_id:, evaluator_name:, score:, passed:, summary: nil, metadata: {})
|
|
14
|
+
execute(
|
|
15
|
+
insert_sql([
|
|
16
|
+
:project_dir, :run_id, :evaluator_name, :score, :passed, :summary, :metadata
|
|
17
|
+
]),
|
|
18
|
+
[
|
|
19
|
+
project_dir,
|
|
20
|
+
run_id,
|
|
21
|
+
evaluator_name,
|
|
22
|
+
score,
|
|
23
|
+
passed ? 1 : 0,
|
|
24
|
+
summary,
|
|
25
|
+
serialize_json(metadata || {})
|
|
26
|
+
]
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
Aidp.log_debug("experience_evaluation_repository", "recorded",
|
|
30
|
+
run_id: run_id,
|
|
31
|
+
evaluator_name: evaluator_name,
|
|
32
|
+
score: score,
|
|
33
|
+
passed: passed)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def list_for_run(run_id)
|
|
37
|
+
rows = query(
|
|
38
|
+
"SELECT * FROM experience_evaluations WHERE run_id = ? AND project_dir = ? ORDER BY id ASC",
|
|
39
|
+
[run_id, project_dir]
|
|
40
|
+
)
|
|
41
|
+
rows.map do |row|
|
|
42
|
+
{
|
|
43
|
+
evaluator_name: row["evaluator_name"],
|
|
44
|
+
score: row["score"],
|
|
45
|
+
passed: row["passed"] == 1,
|
|
46
|
+
summary: row["summary"],
|
|
47
|
+
metadata: deserialize_json(row["metadata"]) || {},
|
|
48
|
+
created_at: row["created_at"]
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require_relative "../repository"
|
|
5
|
+
|
|
6
|
+
module Aidp
|
|
7
|
+
module Database
|
|
8
|
+
module Repositories
|
|
9
|
+
class ExperienceRunRepository < Repository
|
|
10
|
+
def initialize(project_dir: Dir.pwd)
|
|
11
|
+
super(project_dir: project_dir, table_name: "experience_runs")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def start(task_id:, strategy_id:, workflow_id:, depth:, input_payload:, parent_run_id: nil, branch_key: nil, metadata: {})
|
|
15
|
+
run_id = SecureRandom.uuid
|
|
16
|
+
|
|
17
|
+
execute(
|
|
18
|
+
insert_sql([
|
|
19
|
+
:id, :project_dir, :task_id, :strategy_id, :workflow_id, :parent_run_id,
|
|
20
|
+
:branch_key, :status, :depth, :input_payload, :metadata
|
|
21
|
+
]),
|
|
22
|
+
[
|
|
23
|
+
run_id,
|
|
24
|
+
project_dir,
|
|
25
|
+
task_id,
|
|
26
|
+
strategy_id,
|
|
27
|
+
workflow_id,
|
|
28
|
+
parent_run_id,
|
|
29
|
+
branch_key,
|
|
30
|
+
"running",
|
|
31
|
+
depth,
|
|
32
|
+
serialize_json(input_payload || {}),
|
|
33
|
+
serialize_json(metadata || {})
|
|
34
|
+
]
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
Aidp.log_debug("experience_run_repository", "started",
|
|
38
|
+
id: run_id,
|
|
39
|
+
task_id: task_id,
|
|
40
|
+
strategy_id: strategy_id,
|
|
41
|
+
branch_key: branch_key,
|
|
42
|
+
depth: depth)
|
|
43
|
+
|
|
44
|
+
load(run_id)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def complete(run_id:, status:, output_payload:, metadata: {})
|
|
48
|
+
merged_metadata = existing_metadata(run_id).merge(metadata || {})
|
|
49
|
+
|
|
50
|
+
execute(
|
|
51
|
+
<<~SQL,
|
|
52
|
+
UPDATE experience_runs
|
|
53
|
+
SET status = ?, output_payload = ?, metadata = ?, completed_at = ?
|
|
54
|
+
WHERE id = ? AND project_dir = ?
|
|
55
|
+
SQL
|
|
56
|
+
[
|
|
57
|
+
status,
|
|
58
|
+
serialize_json(output_payload || {}),
|
|
59
|
+
serialize_json(merged_metadata),
|
|
60
|
+
current_timestamp,
|
|
61
|
+
run_id,
|
|
62
|
+
project_dir
|
|
63
|
+
]
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
Aidp.log_debug("experience_run_repository", "completed",
|
|
67
|
+
id: run_id, status: status)
|
|
68
|
+
|
|
69
|
+
load(run_id)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def load(run_id)
|
|
73
|
+
row = query_one(
|
|
74
|
+
"SELECT * FROM experience_runs WHERE id = ? AND project_dir = ?",
|
|
75
|
+
[run_id, project_dir]
|
|
76
|
+
)
|
|
77
|
+
deserialize_run(row)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def bundle_for_replay(run_id)
|
|
81
|
+
row = query_one(
|
|
82
|
+
<<~SQL,
|
|
83
|
+
SELECT runs.*, tasks.title AS task_title, tasks.description AS task_description,
|
|
84
|
+
tasks.input_payload AS task_input_payload, tasks.context AS task_context,
|
|
85
|
+
tasks.source_run_id AS task_source_run_id
|
|
86
|
+
FROM experience_runs runs
|
|
87
|
+
JOIN experience_tasks tasks ON tasks.id = runs.task_id
|
|
88
|
+
WHERE runs.id = ? AND runs.project_dir = ?
|
|
89
|
+
SQL
|
|
90
|
+
[run_id, project_dir]
|
|
91
|
+
)
|
|
92
|
+
return nil unless row
|
|
93
|
+
|
|
94
|
+
run = deserialize_run(row)
|
|
95
|
+
run.merge(
|
|
96
|
+
task: {
|
|
97
|
+
id: row["task_id"],
|
|
98
|
+
title: row["task_title"],
|
|
99
|
+
description: row["task_description"],
|
|
100
|
+
input_payload: deserialize_json(row["task_input_payload"]) || {},
|
|
101
|
+
context: deserialize_json(row["task_context"]) || {},
|
|
102
|
+
source_run_id: row["task_source_run_id"]
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def children(parent_run_id)
|
|
108
|
+
rows = query(
|
|
109
|
+
"SELECT * FROM experience_runs WHERE parent_run_id = ? AND project_dir = ? ORDER BY started_at ASC",
|
|
110
|
+
[parent_run_id, project_dir]
|
|
111
|
+
)
|
|
112
|
+
rows.map { |row| deserialize_run(row) }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
private
|
|
116
|
+
|
|
117
|
+
def existing_metadata(run_id)
|
|
118
|
+
load(run_id)&.fetch(:metadata, {}) || {}
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def deserialize_run(row)
|
|
122
|
+
return nil unless row
|
|
123
|
+
|
|
124
|
+
{
|
|
125
|
+
id: row["id"],
|
|
126
|
+
task_id: row["task_id"],
|
|
127
|
+
strategy_id: row["strategy_id"],
|
|
128
|
+
workflow_id: row["workflow_id"],
|
|
129
|
+
parent_run_id: row["parent_run_id"],
|
|
130
|
+
branch_key: row["branch_key"],
|
|
131
|
+
status: row["status"],
|
|
132
|
+
depth: row["depth"],
|
|
133
|
+
input_payload: deserialize_json(row["input_payload"]) || {},
|
|
134
|
+
output_payload: deserialize_json(row["output_payload"]) || {},
|
|
135
|
+
metadata: deserialize_json(row["metadata"]) || {},
|
|
136
|
+
started_at: row["started_at"],
|
|
137
|
+
completed_at: row["completed_at"]
|
|
138
|
+
}
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require_relative "../repository"
|
|
5
|
+
|
|
6
|
+
module Aidp
|
|
7
|
+
module Database
|
|
8
|
+
module Repositories
|
|
9
|
+
class ExperienceTaskRepository < Repository
|
|
10
|
+
def initialize(project_dir: Dir.pwd)
|
|
11
|
+
super(project_dir: project_dir, table_name: "experience_tasks")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create(description:, title: nil, input_payload: {}, context: {}, source_run_id: nil)
|
|
15
|
+
task_id = SecureRandom.uuid
|
|
16
|
+
|
|
17
|
+
execute(
|
|
18
|
+
insert_sql([:id, :project_dir, :title, :description, :input_payload, :context, :source_run_id]),
|
|
19
|
+
[
|
|
20
|
+
task_id,
|
|
21
|
+
project_dir,
|
|
22
|
+
title,
|
|
23
|
+
description,
|
|
24
|
+
serialize_json(input_payload || {}),
|
|
25
|
+
serialize_json(context || {}),
|
|
26
|
+
source_run_id
|
|
27
|
+
]
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
Aidp.log_debug("experience_task_repository", "created",
|
|
31
|
+
id: task_id, source_run_id: source_run_id)
|
|
32
|
+
|
|
33
|
+
load(task_id)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def load(task_id)
|
|
37
|
+
row = query_one(
|
|
38
|
+
"SELECT * FROM experience_tasks WHERE id = ? AND project_dir = ?",
|
|
39
|
+
[task_id, project_dir]
|
|
40
|
+
)
|
|
41
|
+
deserialize_task(row)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def deserialize_task(row)
|
|
47
|
+
return nil unless row
|
|
48
|
+
|
|
49
|
+
{
|
|
50
|
+
id: row["id"],
|
|
51
|
+
title: row["title"],
|
|
52
|
+
description: row["description"],
|
|
53
|
+
input_payload: deserialize_json(row["input_payload"]) || {},
|
|
54
|
+
context: deserialize_json(row["context"]) || {},
|
|
55
|
+
source_run_id: row["source_run_id"],
|
|
56
|
+
created_at: row["created_at"]
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require_relative "../repository"
|
|
5
|
+
|
|
6
|
+
module Aidp
|
|
7
|
+
module Database
|
|
8
|
+
module Repositories
|
|
9
|
+
class StrategyRepository < Repository
|
|
10
|
+
def initialize(project_dir: Dir.pwd)
|
|
11
|
+
super(project_dir: project_dir, table_name: "strategies")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def register(name:, spec:)
|
|
15
|
+
strategy_id = Digest::SHA256.hexdigest("#{project_dir}:#{name}:#{spec}")
|
|
16
|
+
|
|
17
|
+
# Concurrent sibling child workflows can register the identical
|
|
18
|
+
# strategy at the same time (same deterministic strategy_id), so
|
|
19
|
+
# the insert must be idempotent rather than a check-then-insert to
|
|
20
|
+
# avoid a primary-key race between them.
|
|
21
|
+
execute(
|
|
22
|
+
"INSERT OR IGNORE INTO #{table_name} (id, project_dir, name, spec) VALUES (?, ?, ?, ?)",
|
|
23
|
+
[strategy_id, project_dir, name, spec]
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
Aidp.log_debug("strategy_repository", "registered", id: strategy_id, name: name)
|
|
27
|
+
|
|
28
|
+
{id: strategy_id, name: name}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def load(id)
|
|
32
|
+
row = query_one("SELECT * FROM strategies WHERE id = ? AND project_dir = ?", [id, project_dir])
|
|
33
|
+
deserialize_strategy(row)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def find_by_name(name)
|
|
37
|
+
row = query_one(
|
|
38
|
+
"SELECT * FROM strategies WHERE project_dir = ? AND name = ? ORDER BY created_at DESC, rowid DESC",
|
|
39
|
+
[project_dir, name]
|
|
40
|
+
)
|
|
41
|
+
deserialize_strategy(row)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def deserialize_strategy(row)
|
|
47
|
+
return nil unless row
|
|
48
|
+
|
|
49
|
+
{
|
|
50
|
+
id: row["id"],
|
|
51
|
+
name: row["name"],
|
|
52
|
+
spec: deserialize_json(row["spec"]) || {},
|
|
53
|
+
created_at: row["created_at"]
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
data/lib/aidp/database/schema.rb
CHANGED
|
@@ -321,11 +321,105 @@ module Aidp
|
|
|
321
321
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_template_versions_unique ON template_versions(project_dir, template_id, version_number);
|
|
322
322
|
SQL
|
|
323
323
|
|
|
324
|
+
# Version 4: Add strategy execution experience store
|
|
325
|
+
V4_STRATEGY_EXECUTION = <<~SQL
|
|
326
|
+
-- Strategy definitions for orchestration-as-data
|
|
327
|
+
CREATE TABLE IF NOT EXISTS strategies (
|
|
328
|
+
id TEXT PRIMARY KEY,
|
|
329
|
+
project_dir TEXT NOT NULL,
|
|
330
|
+
name TEXT NOT NULL,
|
|
331
|
+
spec TEXT NOT NULL,
|
|
332
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
333
|
+
);
|
|
334
|
+
CREATE INDEX IF NOT EXISTS idx_strategies_project_name ON strategies(project_dir, name);
|
|
335
|
+
|
|
336
|
+
-- Replayable task inputs for strategy execution
|
|
337
|
+
CREATE TABLE IF NOT EXISTS experience_tasks (
|
|
338
|
+
id TEXT PRIMARY KEY,
|
|
339
|
+
project_dir TEXT NOT NULL,
|
|
340
|
+
title TEXT,
|
|
341
|
+
description TEXT NOT NULL,
|
|
342
|
+
input_payload TEXT,
|
|
343
|
+
context TEXT,
|
|
344
|
+
source_run_id TEXT,
|
|
345
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
346
|
+
);
|
|
347
|
+
CREATE INDEX IF NOT EXISTS idx_experience_tasks_project ON experience_tasks(project_dir);
|
|
348
|
+
CREATE INDEX IF NOT EXISTS idx_experience_tasks_source_run ON experience_tasks(source_run_id);
|
|
349
|
+
|
|
350
|
+
-- Individual strategy execution runs, including speculative branches
|
|
351
|
+
CREATE TABLE IF NOT EXISTS experience_runs (
|
|
352
|
+
id TEXT PRIMARY KEY,
|
|
353
|
+
project_dir TEXT NOT NULL,
|
|
354
|
+
task_id TEXT NOT NULL,
|
|
355
|
+
strategy_id TEXT NOT NULL,
|
|
356
|
+
workflow_id TEXT,
|
|
357
|
+
parent_run_id TEXT,
|
|
358
|
+
branch_key TEXT,
|
|
359
|
+
status TEXT NOT NULL,
|
|
360
|
+
depth INTEGER DEFAULT 0,
|
|
361
|
+
input_payload TEXT,
|
|
362
|
+
output_payload TEXT,
|
|
363
|
+
metadata TEXT,
|
|
364
|
+
started_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
365
|
+
completed_at TEXT
|
|
366
|
+
);
|
|
367
|
+
CREATE INDEX IF NOT EXISTS idx_experience_runs_project ON experience_runs(project_dir);
|
|
368
|
+
CREATE INDEX IF NOT EXISTS idx_experience_runs_task ON experience_runs(task_id);
|
|
369
|
+
CREATE INDEX IF NOT EXISTS idx_experience_runs_parent ON experience_runs(parent_run_id);
|
|
370
|
+
CREATE INDEX IF NOT EXISTS idx_experience_runs_strategy ON experience_runs(strategy_id);
|
|
371
|
+
|
|
372
|
+
-- Evaluation signals captured for each run
|
|
373
|
+
CREATE TABLE IF NOT EXISTS experience_evaluations (
|
|
374
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
375
|
+
project_dir TEXT NOT NULL,
|
|
376
|
+
run_id TEXT NOT NULL,
|
|
377
|
+
evaluator_name TEXT NOT NULL,
|
|
378
|
+
score REAL,
|
|
379
|
+
passed INTEGER DEFAULT 0 CHECK (passed IN (0, 1)),
|
|
380
|
+
summary TEXT,
|
|
381
|
+
metadata TEXT,
|
|
382
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
383
|
+
);
|
|
384
|
+
CREATE INDEX IF NOT EXISTS idx_experience_evaluations_run ON experience_evaluations(run_id);
|
|
385
|
+
CREATE INDEX IF NOT EXISTS idx_experience_evaluations_name ON experience_evaluations(project_dir, evaluator_name);
|
|
386
|
+
|
|
387
|
+
-- Artifact metadata produced by runs
|
|
388
|
+
CREATE TABLE IF NOT EXISTS experience_artifacts (
|
|
389
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
390
|
+
project_dir TEXT NOT NULL,
|
|
391
|
+
run_id TEXT NOT NULL,
|
|
392
|
+
role TEXT NOT NULL,
|
|
393
|
+
path TEXT NOT NULL,
|
|
394
|
+
metadata TEXT,
|
|
395
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
396
|
+
);
|
|
397
|
+
CREATE INDEX IF NOT EXISTS idx_experience_artifacts_run ON experience_artifacts(run_id);
|
|
398
|
+
|
|
399
|
+
-- Embeddings are stored as JSON vectors until a dedicated vector store exists
|
|
400
|
+
CREATE TABLE IF NOT EXISTS experience_embeddings (
|
|
401
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
402
|
+
project_dir TEXT NOT NULL,
|
|
403
|
+
task_id TEXT,
|
|
404
|
+
run_id TEXT,
|
|
405
|
+
embedding_type TEXT NOT NULL,
|
|
406
|
+
vector TEXT NOT NULL,
|
|
407
|
+
metadata TEXT,
|
|
408
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
409
|
+
);
|
|
410
|
+
CREATE INDEX IF NOT EXISTS idx_experience_embeddings_task ON experience_embeddings(task_id);
|
|
411
|
+
CREATE INDEX IF NOT EXISTS idx_experience_embeddings_run ON experience_embeddings(run_id);
|
|
412
|
+
SQL
|
|
413
|
+
|
|
324
414
|
# All migrations in order
|
|
415
|
+
# V4's strategies index is intentionally non-unique so that immutable
|
|
416
|
+
# versions of a named strategy can coexist for replay and auditability;
|
|
417
|
+
# StrategyRepository keys rows by a SHA256(project_dir:name:spec) digest.
|
|
325
418
|
MIGRATIONS = {
|
|
326
419
|
1 => V1_INITIAL,
|
|
327
420
|
2 => V2_PROMPT_FEEDBACK,
|
|
328
|
-
3 => V3_TEMPLATE_VERSIONS
|
|
421
|
+
3 => V3_TEMPLATE_VERSIONS,
|
|
422
|
+
4 => V4_STRATEGY_EXECUTION
|
|
329
423
|
}.freeze
|
|
330
424
|
|
|
331
425
|
# Get SQL for a specific version
|