aidp 0.47.1 → 0.49.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/devcontainer_commands.rb +33 -1
- 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/harness/configuration.rb +12 -7
- data/lib/aidp/setup/wizard.rb +625 -68
- 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: 0c5a91840d333d7b33122434b8cc2efcad0149becf640e3932d5d1ea6fd799f1
|
|
4
|
+
data.tar.gz: 0604c7d3798c6dc90aa1ccf543916e73e63fabdd4bcc58ae1a0864cbb0277a0a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8dbaefb32e45cd9687524dea4cd72dc4c38eb13bb766f7e5b476e67481338ad76e6b6c72c0a80cd1e479487701266036c8d1b8cbab74ae288ff44ccaa5d891ac
|
|
7
|
+
data.tar.gz: b2de14911ec9b019235a4966d74208a61d5e2970a9fd710b31d0b58d2ec6f741bbdd6eb0a8314a6fbc7916bbd3c24c6352efa577080d356001dbd583b4aed6f3
|
|
@@ -280,13 +280,45 @@ module Aidp
|
|
|
280
280
|
# Extract wizard-compatible config including custom_ports
|
|
281
281
|
{
|
|
282
282
|
providers: aidp_config.dig("providers")&.keys,
|
|
283
|
-
test_framework: aidp_config
|
|
283
|
+
test_framework: extract_test_framework_for_generation(aidp_config),
|
|
284
284
|
linters: aidp_config.dig("work_loop", "linting", "tools"),
|
|
285
285
|
watch_mode: aidp_config.dig("work_loop", "watch", "enabled"),
|
|
286
286
|
custom_ports: aidp_config.dig("devcontainer", "custom_ports")
|
|
287
287
|
}.compact
|
|
288
288
|
end
|
|
289
289
|
|
|
290
|
+
def extract_test_framework_for_generation(aidp_config)
|
|
291
|
+
framework_from_work_loop_commands(aidp_config) ||
|
|
292
|
+
aidp_config.dig("work_loop", "test_commands", 0, "framework")
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def framework_from_work_loop_commands(aidp_config)
|
|
296
|
+
candidates = Array(aidp_config.dig("work_loop", "commands"))
|
|
297
|
+
command_text = tagged_test_command(candidates) || first_recognized_string_command(candidates)
|
|
298
|
+
framework = Aidp::ToolingDetector.framework_from_command(command_text)
|
|
299
|
+
return if framework == :unknown
|
|
300
|
+
|
|
301
|
+
framework.to_s
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# `work_loop.commands` entries may be hashes with a category, or bare
|
|
305
|
+
# command strings (no category available). Prefer an explicitly tagged
|
|
306
|
+
# test command; when only bare strings are present, fall back to the
|
|
307
|
+
# first one whose command text matches a known test framework.
|
|
308
|
+
def tagged_test_command(candidates)
|
|
309
|
+
candidates.find do |candidate|
|
|
310
|
+
candidate.is_a?(Hash) &&
|
|
311
|
+
# `generate_yaml` writes category as a symbol (e.g. `:test`); YAML
|
|
312
|
+
# preserves symbols on load, so coerce to string before comparing.
|
|
313
|
+
candidate["category"].to_s == "test"
|
|
314
|
+
end&.dig("command")
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def first_recognized_string_command(candidates)
|
|
318
|
+
candidates.select { |candidate| candidate.is_a?(String) }
|
|
319
|
+
.find { |command| Aidp::ToolingDetector.framework_from_command(command) != :unknown }
|
|
320
|
+
end
|
|
321
|
+
|
|
290
322
|
def build_config_from_aidp_yml(devcontainer_config)
|
|
291
323
|
config = {}
|
|
292
324
|
|
|
@@ -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
|