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,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../repository"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
module Database
|
|
7
|
+
module Repositories
|
|
8
|
+
# Repository for harness_state table
|
|
9
|
+
# Replaces harness/*_state.json files
|
|
10
|
+
class HarnessStateRepository < Repository
|
|
11
|
+
def initialize(project_dir: Dir.pwd)
|
|
12
|
+
super(project_dir: project_dir, table_name: "harness_state")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Check if state exists for mode
|
|
16
|
+
#
|
|
17
|
+
# @param mode [String, Symbol] Mode (execute, analyze, etc.)
|
|
18
|
+
# @return [Boolean]
|
|
19
|
+
def has_state?(mode)
|
|
20
|
+
mode_str = mode.to_s
|
|
21
|
+
count = query_value(
|
|
22
|
+
"SELECT COUNT(*) FROM harness_state WHERE project_dir = ? AND mode = ?",
|
|
23
|
+
[project_dir, mode_str]
|
|
24
|
+
)
|
|
25
|
+
count.positive?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Load state for mode
|
|
29
|
+
#
|
|
30
|
+
# @param mode [String, Symbol] Mode
|
|
31
|
+
# @return [Hash] State data (empty hash if no state)
|
|
32
|
+
def load_state(mode)
|
|
33
|
+
mode_str = mode.to_s
|
|
34
|
+
row = query_one(
|
|
35
|
+
"SELECT * FROM harness_state WHERE project_dir = ? AND mode = ?",
|
|
36
|
+
[project_dir, mode_str]
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
return {} unless row
|
|
40
|
+
|
|
41
|
+
state = deserialize_json(row["state"]) || {}
|
|
42
|
+
Aidp.log_debug("harness_state_repository", "loaded",
|
|
43
|
+
mode: mode_str, keys: state.keys.size)
|
|
44
|
+
state
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Save state for mode
|
|
48
|
+
#
|
|
49
|
+
# @param mode [String, Symbol] Mode
|
|
50
|
+
# @param state_data [Hash] State to save
|
|
51
|
+
def save_state(mode, state_data)
|
|
52
|
+
mode_str = mode.to_s
|
|
53
|
+
now = current_timestamp
|
|
54
|
+
|
|
55
|
+
# Add metadata
|
|
56
|
+
state_with_metadata = state_data.merge(
|
|
57
|
+
mode: mode_str,
|
|
58
|
+
project_dir: project_dir,
|
|
59
|
+
saved_at: now
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
existing = query_one(
|
|
63
|
+
"SELECT id, version FROM harness_state WHERE project_dir = ? AND mode = ?",
|
|
64
|
+
[project_dir, mode_str]
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if existing
|
|
68
|
+
new_version = (existing["version"] || 1) + 1
|
|
69
|
+
execute(
|
|
70
|
+
<<~SQL,
|
|
71
|
+
UPDATE harness_state SET
|
|
72
|
+
state = ?,
|
|
73
|
+
version = ?,
|
|
74
|
+
updated_at = ?
|
|
75
|
+
WHERE project_dir = ? AND mode = ?
|
|
76
|
+
SQL
|
|
77
|
+
[
|
|
78
|
+
serialize_json(state_with_metadata),
|
|
79
|
+
new_version,
|
|
80
|
+
now,
|
|
81
|
+
project_dir,
|
|
82
|
+
mode_str
|
|
83
|
+
]
|
|
84
|
+
)
|
|
85
|
+
Aidp.log_debug("harness_state_repository", "updated",
|
|
86
|
+
mode: mode_str, version: new_version)
|
|
87
|
+
else
|
|
88
|
+
execute(
|
|
89
|
+
insert_sql([:project_dir, :mode, :state, :version, :created_at, :updated_at]),
|
|
90
|
+
[
|
|
91
|
+
project_dir,
|
|
92
|
+
mode_str,
|
|
93
|
+
serialize_json(state_with_metadata),
|
|
94
|
+
1,
|
|
95
|
+
now,
|
|
96
|
+
now
|
|
97
|
+
]
|
|
98
|
+
)
|
|
99
|
+
Aidp.log_debug("harness_state_repository", "created", mode: mode_str)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Clear state for mode
|
|
104
|
+
#
|
|
105
|
+
# @param mode [String, Symbol] Mode
|
|
106
|
+
def clear_state(mode)
|
|
107
|
+
mode_str = mode.to_s
|
|
108
|
+
execute(
|
|
109
|
+
"DELETE FROM harness_state WHERE project_dir = ? AND mode = ?",
|
|
110
|
+
[project_dir, mode_str]
|
|
111
|
+
)
|
|
112
|
+
Aidp.log_debug("harness_state_repository", "cleared", mode: mode_str)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Get all modes with state
|
|
116
|
+
#
|
|
117
|
+
# @return [Array<String>] Mode names
|
|
118
|
+
def modes_with_state
|
|
119
|
+
rows = query(
|
|
120
|
+
"SELECT DISTINCT mode FROM harness_state WHERE project_dir = ?",
|
|
121
|
+
[project_dir]
|
|
122
|
+
)
|
|
123
|
+
rows.map { |r| r["mode"] }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Get state version for mode
|
|
127
|
+
#
|
|
128
|
+
# @param mode [String, Symbol] Mode
|
|
129
|
+
# @return [Integer] Version number (0 if no state)
|
|
130
|
+
def version(mode)
|
|
131
|
+
mode_str = mode.to_s
|
|
132
|
+
query_value(
|
|
133
|
+
"SELECT version FROM harness_state WHERE project_dir = ? AND mode = ?",
|
|
134
|
+
[project_dir, mode_str]
|
|
135
|
+
) || 0
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../repository"
|
|
4
|
+
require "securerandom"
|
|
5
|
+
|
|
6
|
+
module Aidp
|
|
7
|
+
module Database
|
|
8
|
+
module Repositories
|
|
9
|
+
# Repository for background_jobs table
|
|
10
|
+
# Replaces jobs/* directory (metadata, logs, PID tracking)
|
|
11
|
+
class JobRepository < Repository
|
|
12
|
+
VALID_STATUSES = %w[pending running completed failed stopped].freeze
|
|
13
|
+
|
|
14
|
+
def initialize(project_dir: Dir.pwd)
|
|
15
|
+
super(project_dir: project_dir, table_name: "background_jobs")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Create a new job
|
|
19
|
+
#
|
|
20
|
+
# @param job_type [String] Type of job (e.g., "execute", "analyze")
|
|
21
|
+
# @param input [Hash] Job input/options
|
|
22
|
+
# @return [String] Job ID
|
|
23
|
+
def create(job_type:, input: {})
|
|
24
|
+
job_id = generate_job_id
|
|
25
|
+
now = current_timestamp
|
|
26
|
+
|
|
27
|
+
execute(
|
|
28
|
+
insert_sql([
|
|
29
|
+
:id, :project_dir, :job_type, :status, :options, :created_at
|
|
30
|
+
]),
|
|
31
|
+
[
|
|
32
|
+
job_id,
|
|
33
|
+
project_dir,
|
|
34
|
+
job_type,
|
|
35
|
+
"pending",
|
|
36
|
+
serialize_json(input),
|
|
37
|
+
now
|
|
38
|
+
]
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
Aidp.log_debug("job_repository", "created",
|
|
42
|
+
job_id: job_id, type: job_type)
|
|
43
|
+
|
|
44
|
+
job_id
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Start a job
|
|
48
|
+
#
|
|
49
|
+
# @param job_id [String] Job ID
|
|
50
|
+
# @param pid [Integer, nil] Process ID
|
|
51
|
+
def start(job_id, pid: nil)
|
|
52
|
+
now = current_timestamp
|
|
53
|
+
|
|
54
|
+
execute(
|
|
55
|
+
<<~SQL,
|
|
56
|
+
UPDATE background_jobs SET
|
|
57
|
+
status = 'running',
|
|
58
|
+
pid = ?,
|
|
59
|
+
started_at = ?
|
|
60
|
+
WHERE id = ? AND project_dir = ?
|
|
61
|
+
SQL
|
|
62
|
+
[pid, now, job_id, project_dir]
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
Aidp.log_debug("job_repository", "started", job_id: job_id, pid: pid)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Complete a job
|
|
69
|
+
#
|
|
70
|
+
# @param job_id [String] Job ID
|
|
71
|
+
# @param output [Hash] Job output/result
|
|
72
|
+
def complete(job_id, output: {})
|
|
73
|
+
now = current_timestamp
|
|
74
|
+
|
|
75
|
+
execute(
|
|
76
|
+
<<~SQL,
|
|
77
|
+
UPDATE background_jobs SET
|
|
78
|
+
status = 'completed',
|
|
79
|
+
result = ?,
|
|
80
|
+
completed_at = ?
|
|
81
|
+
WHERE id = ? AND project_dir = ?
|
|
82
|
+
SQL
|
|
83
|
+
[serialize_json(output), now, job_id, project_dir]
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
Aidp.log_debug("job_repository", "completed", job_id: job_id)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Fail a job
|
|
90
|
+
#
|
|
91
|
+
# @param job_id [String] Job ID
|
|
92
|
+
# @param error [String] Error message
|
|
93
|
+
def fail(job_id, error:)
|
|
94
|
+
now = current_timestamp
|
|
95
|
+
|
|
96
|
+
execute(
|
|
97
|
+
<<~SQL,
|
|
98
|
+
UPDATE background_jobs SET
|
|
99
|
+
status = 'failed',
|
|
100
|
+
error = ?,
|
|
101
|
+
completed_at = ?
|
|
102
|
+
WHERE id = ? AND project_dir = ?
|
|
103
|
+
SQL
|
|
104
|
+
[error, now, job_id, project_dir]
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
Aidp.log_debug("job_repository", "failed", job_id: job_id, error: error)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Stop a job
|
|
111
|
+
#
|
|
112
|
+
# @param job_id [String] Job ID
|
|
113
|
+
def stop(job_id)
|
|
114
|
+
now = current_timestamp
|
|
115
|
+
|
|
116
|
+
execute(
|
|
117
|
+
<<~SQL,
|
|
118
|
+
UPDATE background_jobs SET
|
|
119
|
+
status = 'stopped',
|
|
120
|
+
completed_at = ?
|
|
121
|
+
WHERE id = ? AND project_dir = ?
|
|
122
|
+
SQL
|
|
123
|
+
[now, job_id, project_dir]
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
Aidp.log_debug("job_repository", "stopped", job_id: job_id)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Find job by ID
|
|
130
|
+
#
|
|
131
|
+
# @param job_id [String] Job ID
|
|
132
|
+
# @return [Hash, nil] Job or nil
|
|
133
|
+
def find(job_id)
|
|
134
|
+
row = query_one(
|
|
135
|
+
"SELECT * FROM background_jobs WHERE id = ? AND project_dir = ?",
|
|
136
|
+
[job_id, project_dir]
|
|
137
|
+
)
|
|
138
|
+
deserialize_job(row)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# List all jobs
|
|
142
|
+
#
|
|
143
|
+
# @param status [String, nil] Filter by status
|
|
144
|
+
# @param limit [Integer] Maximum jobs
|
|
145
|
+
# @return [Array<Hash>] Jobs
|
|
146
|
+
def list(status: nil, limit: 50)
|
|
147
|
+
rows = if status
|
|
148
|
+
query(
|
|
149
|
+
<<~SQL,
|
|
150
|
+
SELECT * FROM background_jobs
|
|
151
|
+
WHERE project_dir = ? AND status = ?
|
|
152
|
+
ORDER BY created_at DESC
|
|
153
|
+
LIMIT ?
|
|
154
|
+
SQL
|
|
155
|
+
[project_dir, status, limit]
|
|
156
|
+
)
|
|
157
|
+
else
|
|
158
|
+
query(
|
|
159
|
+
<<~SQL,
|
|
160
|
+
SELECT * FROM background_jobs
|
|
161
|
+
WHERE project_dir = ?
|
|
162
|
+
ORDER BY created_at DESC
|
|
163
|
+
LIMIT ?
|
|
164
|
+
SQL
|
|
165
|
+
[project_dir, limit]
|
|
166
|
+
)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
rows.map { |row| deserialize_job(row) }
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# List running jobs
|
|
173
|
+
#
|
|
174
|
+
# @return [Array<Hash>]
|
|
175
|
+
def running
|
|
176
|
+
list(status: "running")
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Get job status
|
|
180
|
+
#
|
|
181
|
+
# @param job_id [String] Job ID
|
|
182
|
+
# @return [Hash, nil] Status info or nil
|
|
183
|
+
def status(job_id)
|
|
184
|
+
job = find(job_id)
|
|
185
|
+
return nil unless job
|
|
186
|
+
|
|
187
|
+
pid = job[:pid]
|
|
188
|
+
|
|
189
|
+
{
|
|
190
|
+
job_id: job_id,
|
|
191
|
+
mode: job[:job_type],
|
|
192
|
+
status: job[:status],
|
|
193
|
+
pid: pid,
|
|
194
|
+
running: pid && process_running?(pid),
|
|
195
|
+
started_at: job[:started_at],
|
|
196
|
+
completed_at: job[:completed_at],
|
|
197
|
+
error: job[:error]
|
|
198
|
+
}
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Delete a job
|
|
202
|
+
#
|
|
203
|
+
# @param job_id [String] Job ID
|
|
204
|
+
def delete(job_id)
|
|
205
|
+
execute(
|
|
206
|
+
"DELETE FROM background_jobs WHERE id = ? AND project_dir = ?",
|
|
207
|
+
[job_id, project_dir]
|
|
208
|
+
)
|
|
209
|
+
Aidp.log_debug("job_repository", "deleted", job_id: job_id)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Cleanup old completed jobs
|
|
213
|
+
#
|
|
214
|
+
# @param days_to_keep [Integer] Days to retain
|
|
215
|
+
# @return [Integer] Deleted count
|
|
216
|
+
def cleanup(days_to_keep: 7)
|
|
217
|
+
threshold = (Time.now - (days_to_keep * 24 * 60 * 60)).strftime("%Y-%m-%d %H:%M:%S")
|
|
218
|
+
|
|
219
|
+
count = query_value(
|
|
220
|
+
<<~SQL,
|
|
221
|
+
SELECT COUNT(*) FROM background_jobs
|
|
222
|
+
WHERE project_dir = ? AND status IN ('completed', 'failed', 'stopped')
|
|
223
|
+
AND completed_at < ?
|
|
224
|
+
SQL
|
|
225
|
+
[project_dir, threshold]
|
|
226
|
+
) || 0
|
|
227
|
+
|
|
228
|
+
execute(
|
|
229
|
+
<<~SQL,
|
|
230
|
+
DELETE FROM background_jobs
|
|
231
|
+
WHERE project_dir = ? AND status IN ('completed', 'failed', 'stopped')
|
|
232
|
+
AND completed_at < ?
|
|
233
|
+
SQL
|
|
234
|
+
[project_dir, threshold]
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
Aidp.log_debug("job_repository", "cleanup",
|
|
238
|
+
deleted: count, threshold: threshold)
|
|
239
|
+
|
|
240
|
+
count
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
private
|
|
244
|
+
|
|
245
|
+
def generate_job_id
|
|
246
|
+
timestamp = Time.now.strftime("%Y%m%d_%H%M%S")
|
|
247
|
+
"job_#{timestamp}_#{SecureRandom.hex(4)}"
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def process_running?(pid)
|
|
251
|
+
return false unless pid
|
|
252
|
+
|
|
253
|
+
Process.kill(0, pid)
|
|
254
|
+
true
|
|
255
|
+
rescue Errno::ESRCH, Errno::EPERM
|
|
256
|
+
false
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def deserialize_job(row)
|
|
260
|
+
return nil unless row
|
|
261
|
+
|
|
262
|
+
{
|
|
263
|
+
id: row["id"],
|
|
264
|
+
job_type: row["job_type"],
|
|
265
|
+
status: row["status"],
|
|
266
|
+
options: deserialize_json(row["options"]) || {},
|
|
267
|
+
result: deserialize_json(row["result"]) || {},
|
|
268
|
+
pid: row["pid"],
|
|
269
|
+
error: row["error"],
|
|
270
|
+
started_at: row["started_at"],
|
|
271
|
+
completed_at: row["completed_at"],
|
|
272
|
+
created_at: row["created_at"]
|
|
273
|
+
}
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../repository"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
module Database
|
|
7
|
+
module Repositories
|
|
8
|
+
# Repository for model cache
|
|
9
|
+
# Replaces: ~/.aidp/cache/models.json
|
|
10
|
+
#
|
|
11
|
+
# Caches discovered models from AI providers with TTL support
|
|
12
|
+
class ModelCacheRepository < Repository
|
|
13
|
+
DEFAULT_TTL = 86400 # 24 hours in seconds
|
|
14
|
+
|
|
15
|
+
def initialize(project_dir: Dir.pwd)
|
|
16
|
+
super(project_dir: project_dir, table_name: "model_cache")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Cache models for a provider
|
|
20
|
+
#
|
|
21
|
+
# @param provider_name [String] Provider name
|
|
22
|
+
# @param models [Array<Hash>] Models array
|
|
23
|
+
# @param ttl [Integer] Time to live in seconds
|
|
24
|
+
# @return [Boolean] Success status
|
|
25
|
+
def cache_models(provider_name, models, ttl: DEFAULT_TTL)
|
|
26
|
+
Aidp.log_debug("model_cache_repo", "cache_models",
|
|
27
|
+
provider: provider_name, count: models.size, ttl: ttl)
|
|
28
|
+
|
|
29
|
+
expires_at = Time.now.utc + ttl
|
|
30
|
+
|
|
31
|
+
execute(<<~SQL, [project_dir, provider_name, serialize_json(models), current_timestamp, expires_at.strftime("%Y-%m-%d %H:%M:%S")])
|
|
32
|
+
INSERT INTO #{table_name} (project_dir, provider_name, models, cached_at, expires_at)
|
|
33
|
+
VALUES (?, ?, ?, ?, ?)
|
|
34
|
+
ON CONFLICT(project_dir, provider_name) DO UPDATE SET
|
|
35
|
+
models = excluded.models,
|
|
36
|
+
cached_at = excluded.cached_at,
|
|
37
|
+
expires_at = excluded.expires_at
|
|
38
|
+
SQL
|
|
39
|
+
|
|
40
|
+
Aidp.log_info("model_cache_repo", "cached_models",
|
|
41
|
+
provider: provider_name, count: models.size)
|
|
42
|
+
true
|
|
43
|
+
rescue => e
|
|
44
|
+
Aidp.log_error("model_cache_repo", "cache_models_failed",
|
|
45
|
+
provider: provider_name, error: e.message)
|
|
46
|
+
false
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Get cached models for a provider if not expired
|
|
50
|
+
#
|
|
51
|
+
# @param provider_name [String] Provider name
|
|
52
|
+
# @return [Array<Hash>, nil] Cached models or nil if expired/not found
|
|
53
|
+
def get_cached_models(provider_name)
|
|
54
|
+
Aidp.log_debug("model_cache_repo", "get_cached_models",
|
|
55
|
+
provider: provider_name)
|
|
56
|
+
|
|
57
|
+
row = query_one(<<~SQL, [project_dir, provider_name])
|
|
58
|
+
SELECT models, cached_at, expires_at
|
|
59
|
+
FROM #{table_name}
|
|
60
|
+
WHERE project_dir = ? AND provider_name = ?
|
|
61
|
+
SQL
|
|
62
|
+
|
|
63
|
+
return nil unless row
|
|
64
|
+
|
|
65
|
+
# Check expiration
|
|
66
|
+
if row["expires_at"]
|
|
67
|
+
# Parse as UTC since timestamps are stored in UTC
|
|
68
|
+
expires_at = Time.parse(row["expires_at"] + " UTC")
|
|
69
|
+
if Time.now.utc > expires_at
|
|
70
|
+
Aidp.log_debug("model_cache_repo", "cache_expired",
|
|
71
|
+
provider: provider_name, expires_at: expires_at)
|
|
72
|
+
return nil
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
models = deserialize_json(row["models"])
|
|
77
|
+
Aidp.log_debug("model_cache_repo", "cache_hit",
|
|
78
|
+
provider: provider_name, count: models&.size || 0)
|
|
79
|
+
models
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Invalidate cache for a specific provider
|
|
83
|
+
#
|
|
84
|
+
# @param provider_name [String] Provider name
|
|
85
|
+
# @return [Boolean] Success status
|
|
86
|
+
def invalidate(provider_name)
|
|
87
|
+
Aidp.log_debug("model_cache_repo", "invalidate",
|
|
88
|
+
provider: provider_name)
|
|
89
|
+
|
|
90
|
+
execute(<<~SQL, [project_dir, provider_name])
|
|
91
|
+
DELETE FROM #{table_name}
|
|
92
|
+
WHERE project_dir = ? AND provider_name = ?
|
|
93
|
+
SQL
|
|
94
|
+
|
|
95
|
+
Aidp.log_info("model_cache_repo", "invalidated",
|
|
96
|
+
provider: provider_name)
|
|
97
|
+
true
|
|
98
|
+
rescue => e
|
|
99
|
+
Aidp.log_error("model_cache_repo", "invalidate_failed",
|
|
100
|
+
provider: provider_name, error: e.message)
|
|
101
|
+
false
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Invalidate all cached models
|
|
105
|
+
#
|
|
106
|
+
# @return [Boolean] Success status
|
|
107
|
+
def invalidate_all
|
|
108
|
+
Aidp.log_debug("model_cache_repo", "invalidate_all")
|
|
109
|
+
delete_by_project
|
|
110
|
+
Aidp.log_info("model_cache_repo", "invalidated_all")
|
|
111
|
+
true
|
|
112
|
+
rescue => e
|
|
113
|
+
Aidp.log_error("model_cache_repo", "invalidate_all_failed",
|
|
114
|
+
error: e.message)
|
|
115
|
+
false
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Get list of providers with valid cached models
|
|
119
|
+
#
|
|
120
|
+
# @return [Array<String>] Provider names with valid caches
|
|
121
|
+
def cached_providers
|
|
122
|
+
rows = query(<<~SQL, [project_dir])
|
|
123
|
+
SELECT provider_name, expires_at
|
|
124
|
+
FROM #{table_name}
|
|
125
|
+
WHERE project_dir = ?
|
|
126
|
+
SQL
|
|
127
|
+
|
|
128
|
+
providers = []
|
|
129
|
+
rows.each do |row|
|
|
130
|
+
next unless row["expires_at"]
|
|
131
|
+
|
|
132
|
+
# Parse as UTC since timestamps are stored in UTC
|
|
133
|
+
expires_at = Time.parse(row["expires_at"] + " UTC")
|
|
134
|
+
providers << row["provider_name"] if Time.now.utc <= expires_at
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
providers
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Get cache statistics
|
|
141
|
+
#
|
|
142
|
+
# @return [Hash] Statistics
|
|
143
|
+
def stats
|
|
144
|
+
total = query_value(<<~SQL, [project_dir])
|
|
145
|
+
SELECT COUNT(*) FROM #{table_name} WHERE project_dir = ?
|
|
146
|
+
SQL
|
|
147
|
+
|
|
148
|
+
valid_providers = cached_providers
|
|
149
|
+
|
|
150
|
+
{
|
|
151
|
+
total_providers: total || 0,
|
|
152
|
+
cached_providers: valid_providers,
|
|
153
|
+
valid_count: valid_providers.size
|
|
154
|
+
}
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Clean up expired entries
|
|
158
|
+
#
|
|
159
|
+
# @return [Integer] Number of entries deleted
|
|
160
|
+
def cleanup_expired
|
|
161
|
+
result = query_value(<<~SQL, [project_dir])
|
|
162
|
+
SELECT COUNT(*) FROM #{table_name}
|
|
163
|
+
WHERE project_dir = ?
|
|
164
|
+
AND expires_at IS NOT NULL
|
|
165
|
+
AND datetime(expires_at) <= datetime('now')
|
|
166
|
+
SQL
|
|
167
|
+
|
|
168
|
+
execute(<<~SQL, [project_dir])
|
|
169
|
+
DELETE FROM #{table_name}
|
|
170
|
+
WHERE project_dir = ?
|
|
171
|
+
AND expires_at IS NOT NULL
|
|
172
|
+
AND datetime(expires_at) <= datetime('now')
|
|
173
|
+
SQL
|
|
174
|
+
|
|
175
|
+
count = result || 0
|
|
176
|
+
if count > 0
|
|
177
|
+
Aidp.log_info("model_cache_repo", "cleanup_expired",
|
|
178
|
+
deleted: count)
|
|
179
|
+
end
|
|
180
|
+
count
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Get model count for a provider
|
|
184
|
+
#
|
|
185
|
+
# @param provider_name [String] Provider name
|
|
186
|
+
# @return [Integer] Number of cached models
|
|
187
|
+
def model_count(provider_name)
|
|
188
|
+
models = get_cached_models(provider_name)
|
|
189
|
+
models&.size || 0
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|