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,253 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tty-prompt"
|
|
4
|
+
require "tty-table"
|
|
5
|
+
require_relative "../prompts/prompt_template_manager"
|
|
6
|
+
require_relative "../message_display"
|
|
7
|
+
|
|
8
|
+
module Aidp
|
|
9
|
+
class CLI
|
|
10
|
+
# Command handler for `aidp prompts` subcommand
|
|
11
|
+
#
|
|
12
|
+
# Provides commands for managing prompt templates:
|
|
13
|
+
# - List all available templates
|
|
14
|
+
# - Show template details
|
|
15
|
+
# - Customize templates for project
|
|
16
|
+
# - Reset customized templates
|
|
17
|
+
#
|
|
18
|
+
# Usage:
|
|
19
|
+
# aidp prompts list
|
|
20
|
+
# aidp prompts show <template_id>
|
|
21
|
+
# aidp prompts customize <template_id>
|
|
22
|
+
# aidp prompts reset <template_id>
|
|
23
|
+
class PromptsCommand
|
|
24
|
+
include Aidp::MessageDisplay
|
|
25
|
+
|
|
26
|
+
def initialize(prompt: TTY::Prompt.new, project_dir: Dir.pwd)
|
|
27
|
+
@prompt = prompt
|
|
28
|
+
@project_dir = project_dir
|
|
29
|
+
@template_manager = Prompts::PromptTemplateManager.new(project_dir: project_dir)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Main entry point for prompts command
|
|
33
|
+
def run(args)
|
|
34
|
+
subcommand = args.shift
|
|
35
|
+
|
|
36
|
+
case subcommand
|
|
37
|
+
when "list", "ls"
|
|
38
|
+
list_templates(args)
|
|
39
|
+
when "show", "view"
|
|
40
|
+
show_template(args)
|
|
41
|
+
when "customize", "edit"
|
|
42
|
+
customize_template(args)
|
|
43
|
+
when "reset"
|
|
44
|
+
reset_template(args)
|
|
45
|
+
when "-h", "--help", nil
|
|
46
|
+
display_usage
|
|
47
|
+
else
|
|
48
|
+
display_message("Unknown subcommand: #{subcommand}", type: :error)
|
|
49
|
+
display_usage
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def list_templates(args)
|
|
56
|
+
category_filter = nil
|
|
57
|
+
|
|
58
|
+
# Parse options
|
|
59
|
+
until args.empty?
|
|
60
|
+
token = args.shift
|
|
61
|
+
case token
|
|
62
|
+
when "--category", "-c"
|
|
63
|
+
category_filter = args.shift
|
|
64
|
+
when "-h", "--help"
|
|
65
|
+
display_list_usage
|
|
66
|
+
return
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
templates = @template_manager.list_templates
|
|
71
|
+
|
|
72
|
+
if templates.empty?
|
|
73
|
+
display_message("No prompt templates found.", type: :info)
|
|
74
|
+
return
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Filter by category if specified
|
|
78
|
+
if category_filter
|
|
79
|
+
templates = templates.select { |t| t[:category] == category_filter }
|
|
80
|
+
if templates.empty?
|
|
81
|
+
display_message("No templates found in category: #{category_filter}", type: :info)
|
|
82
|
+
return
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Group by category
|
|
87
|
+
grouped = templates.group_by { |t| t[:category] }
|
|
88
|
+
|
|
89
|
+
display_message("\nAvailable Prompt Templates", type: :highlight)
|
|
90
|
+
display_message("=" * 50, type: :muted)
|
|
91
|
+
|
|
92
|
+
grouped.each do |category, category_templates|
|
|
93
|
+
display_message("\n#{category}/", type: :info)
|
|
94
|
+
|
|
95
|
+
table_data = category_templates.map do |t|
|
|
96
|
+
source_badge = case @template_manager.determine_source(t[:path])
|
|
97
|
+
when :project then "[project]"
|
|
98
|
+
when :user then "[user]"
|
|
99
|
+
else "[builtin]"
|
|
100
|
+
end
|
|
101
|
+
[
|
|
102
|
+
" #{File.basename(t[:id])}",
|
|
103
|
+
t[:version] || "1.0.0",
|
|
104
|
+
source_badge,
|
|
105
|
+
truncate(t[:description] || "", 40)
|
|
106
|
+
]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
table = TTY::Table.new(
|
|
110
|
+
header: ["Template", "Version", "Source", "Description"],
|
|
111
|
+
rows: table_data
|
|
112
|
+
)
|
|
113
|
+
display_message(table.render(:unicode, padding: [0, 1]), type: :info)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
display_message("\nUse 'aidp prompts show <template_id>' to view details", type: :muted)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def show_template(args)
|
|
120
|
+
template_id = args.shift
|
|
121
|
+
|
|
122
|
+
if template_id.nil? || template_id.start_with?("-")
|
|
123
|
+
display_message("Error: Template ID required", type: :error)
|
|
124
|
+
display_show_usage
|
|
125
|
+
return
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
info = @template_manager.template_info(template_id)
|
|
129
|
+
|
|
130
|
+
if info.nil?
|
|
131
|
+
display_message("Template not found: #{template_id}", type: :error)
|
|
132
|
+
display_message("Use 'aidp prompts list' to see available templates", type: :muted)
|
|
133
|
+
return
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
display_message("\nTemplate: #{info[:id]}", type: :highlight)
|
|
137
|
+
display_message("=" * 50, type: :muted)
|
|
138
|
+
display_message("Name: #{info[:name]}", type: :info)
|
|
139
|
+
display_message("Description: #{info[:description]}", type: :info) if info[:description]
|
|
140
|
+
display_message("Version: #{info[:version]}", type: :info) if info[:version]
|
|
141
|
+
display_message("Source: #{info[:source]}", type: :info)
|
|
142
|
+
display_message("Path: #{info[:path]}", type: :muted)
|
|
143
|
+
|
|
144
|
+
if info[:variables]&.any?
|
|
145
|
+
display_message("\nVariables:", type: :highlight)
|
|
146
|
+
info[:variables].each { |v| display_message(" - {{#{v}}}", type: :info) }
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
display_message("\nPrompt Preview:", type: :highlight)
|
|
150
|
+
display_message("-" * 50, type: :muted)
|
|
151
|
+
display_message(info[:prompt_preview] || "(empty)", type: :info)
|
|
152
|
+
display_message("-" * 50, type: :muted)
|
|
153
|
+
|
|
154
|
+
if info[:source] != :project
|
|
155
|
+
display_message("\nTo customize this template for your project:", type: :muted)
|
|
156
|
+
display_message(" aidp prompts customize #{template_id}", type: :muted)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def customize_template(args)
|
|
161
|
+
template_id = args.shift
|
|
162
|
+
|
|
163
|
+
if template_id.nil? || template_id.start_with?("-")
|
|
164
|
+
display_message("Error: Template ID required", type: :error)
|
|
165
|
+
display_customize_usage
|
|
166
|
+
return
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
begin
|
|
170
|
+
path = @template_manager.customize_template(template_id)
|
|
171
|
+
display_message("Template customized successfully!", type: :success)
|
|
172
|
+
display_message("Edit the template at: #{path}", type: :info)
|
|
173
|
+
display_message("\nThe template will now be loaded from your project directory.", type: :muted)
|
|
174
|
+
display_message("Use 'aidp prompts reset #{template_id}' to restore the default.", type: :muted)
|
|
175
|
+
rescue Prompts::TemplateNotFoundError
|
|
176
|
+
display_message("Template not found: #{template_id}", type: :error)
|
|
177
|
+
display_message("Use 'aidp prompts list' to see available templates", type: :muted)
|
|
178
|
+
rescue => e
|
|
179
|
+
display_message("Failed to customize template: #{e.message}", type: :error)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def reset_template(args)
|
|
184
|
+
template_id = args.shift
|
|
185
|
+
|
|
186
|
+
if template_id.nil? || template_id.start_with?("-")
|
|
187
|
+
display_message("Error: Template ID required", type: :error)
|
|
188
|
+
display_reset_usage
|
|
189
|
+
return
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Confirm reset
|
|
193
|
+
unless @prompt.yes?("Reset #{template_id} to default? This will delete your customizations.")
|
|
194
|
+
display_message("Reset cancelled", type: :info)
|
|
195
|
+
return
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
if @template_manager.reset_template(template_id)
|
|
199
|
+
display_message("Template reset to default successfully!", type: :success)
|
|
200
|
+
else
|
|
201
|
+
display_message("No customization found for: #{template_id}", type: :info)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def truncate(text, max_length)
|
|
206
|
+
return "" if text.nil?
|
|
207
|
+
return text if text.length <= max_length
|
|
208
|
+
|
|
209
|
+
"#{text[0, max_length - 3]}..."
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def display_usage
|
|
213
|
+
display_message("\nUsage: aidp prompts <subcommand> [options]", type: :info)
|
|
214
|
+
display_message("\nSubcommands:", type: :info)
|
|
215
|
+
display_message(" list, ls List all available prompt templates", type: :info)
|
|
216
|
+
display_message(" show, view <id> Show template details", type: :info)
|
|
217
|
+
display_message(" customize, edit <id> Copy template to project for customization", type: :info)
|
|
218
|
+
display_message(" reset <id> Reset customized template to default", type: :info)
|
|
219
|
+
display_message("\nExamples:", type: :info)
|
|
220
|
+
display_message(" aidp prompts list", type: :info)
|
|
221
|
+
display_message(" aidp prompts list --category decision_engine", type: :info)
|
|
222
|
+
display_message(" aidp prompts show decision_engine/condition_detection", type: :info)
|
|
223
|
+
display_message(" aidp prompts customize decision_engine/condition_detection", type: :info)
|
|
224
|
+
display_message(" aidp prompts reset decision_engine/condition_detection", type: :info)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def display_list_usage
|
|
228
|
+
display_message("\nUsage: aidp prompts list [options]", type: :info)
|
|
229
|
+
display_message("\nOptions:", type: :info)
|
|
230
|
+
display_message(" --category, -c <name> Filter by category", type: :info)
|
|
231
|
+
display_message(" -h, --help Show this help", type: :info)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def display_show_usage
|
|
235
|
+
display_message("\nUsage: aidp prompts show <template_id>", type: :info)
|
|
236
|
+
display_message("\nExamples:", type: :info)
|
|
237
|
+
display_message(" aidp prompts show decision_engine/condition_detection", type: :info)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def display_customize_usage
|
|
241
|
+
display_message("\nUsage: aidp prompts customize <template_id>", type: :info)
|
|
242
|
+
display_message("\nExamples:", type: :info)
|
|
243
|
+
display_message(" aidp prompts customize decision_engine/condition_detection", type: :info)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def display_reset_usage
|
|
247
|
+
display_message("\nUsage: aidp prompts reset <template_id>", type: :info)
|
|
248
|
+
display_message("\nExamples:", type: :info)
|
|
249
|
+
display_message(" aidp prompts reset decision_engine/condition_detection", type: :info)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
@@ -160,9 +160,7 @@ module Aidp
|
|
|
160
160
|
# Check if env var exists
|
|
161
161
|
unless ENV.key?(env_var)
|
|
162
162
|
@prompt.warn("Warning: Environment variable '#{env_var}' is not currently set")
|
|
163
|
-
unless @prompt.yes?("Continue anyway?")
|
|
164
|
-
return 1
|
|
165
|
-
end
|
|
163
|
+
return 1 unless @prompt.yes?("Continue anyway?")
|
|
166
164
|
end
|
|
167
165
|
|
|
168
166
|
# Ask for optional description
|
|
@@ -189,9 +187,7 @@ module Aidp
|
|
|
189
187
|
@prompt.say(" Env var: #{env_var}")
|
|
190
188
|
@prompt.say(" Registered at: #{result[:registered_at]}")
|
|
191
189
|
|
|
192
|
-
if scope_list.any?
|
|
193
|
-
@prompt.say(" Scopes: #{scope_list.join(", ")}")
|
|
194
|
-
end
|
|
190
|
+
@prompt.say(" Scopes: #{scope_list.join(", ")}") if scope_list.any?
|
|
195
191
|
|
|
196
192
|
@prompt.say("\nThe secret value will be proxied through short-lived tokens.")
|
|
197
193
|
@prompt.say("Agent processes will not have direct access to '#{env_var}'.")
|
|
@@ -229,9 +225,7 @@ module Aidp
|
|
|
229
225
|
|
|
230
226
|
if registry.unregister(name: secret_name)
|
|
231
227
|
@prompt.ok("Secret '#{secret_name}' unregistered")
|
|
232
|
-
if revoked_count > 0
|
|
233
|
-
@prompt.say(" Revoked #{revoked_count} active token(s)")
|
|
234
|
-
end
|
|
228
|
+
@prompt.say(" Revoked #{revoked_count} active token(s)") if revoked_count > 0
|
|
235
229
|
0
|
|
236
230
|
else
|
|
237
231
|
@prompt.error("Failed to unregister secret")
|
|
@@ -349,7 +343,7 @@ module Aidp
|
|
|
349
343
|
end
|
|
350
344
|
|
|
351
345
|
# Run RSpec with timeout protection
|
|
352
|
-
cmd = "bundle exec rspec #{rspec_path} --format
|
|
346
|
+
cmd = "bundle exec rspec #{rspec_path} --format failures"
|
|
353
347
|
@prompt.say("$ #{cmd}\n")
|
|
354
348
|
@prompt.say("(timeout: #{AUDIT_TIMEOUT_SECONDS / 60} minutes)\n")
|
|
355
349
|
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tty-prompt"
|
|
4
|
+
require_relative "../database/storage_migrator"
|
|
5
|
+
|
|
6
|
+
module Aidp
|
|
7
|
+
class CLI
|
|
8
|
+
# Command handler for `aidp storage` subcommand
|
|
9
|
+
#
|
|
10
|
+
# Provides commands for managing AIDP storage:
|
|
11
|
+
# - migrate: Migrate file-based storage to SQLite
|
|
12
|
+
# - status: Show storage migration status
|
|
13
|
+
# - cleanup: Remove old file-based storage after migration
|
|
14
|
+
#
|
|
15
|
+
# Usage:
|
|
16
|
+
# aidp storage migrate
|
|
17
|
+
# aidp storage migrate --dry-run
|
|
18
|
+
# aidp storage migrate --no-backup
|
|
19
|
+
# aidp storage status
|
|
20
|
+
# aidp storage cleanup
|
|
21
|
+
class StorageCommand
|
|
22
|
+
include Aidp::MessageDisplay
|
|
23
|
+
|
|
24
|
+
def initialize(prompt: TTY::Prompt.new, project_dir: nil)
|
|
25
|
+
@prompt = prompt
|
|
26
|
+
@project_dir = project_dir || Dir.pwd
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Main entry point for storage command
|
|
30
|
+
def run(args)
|
|
31
|
+
subcommand = args.shift
|
|
32
|
+
|
|
33
|
+
case subcommand
|
|
34
|
+
when "migrate"
|
|
35
|
+
run_migrate(args)
|
|
36
|
+
when "status"
|
|
37
|
+
run_status
|
|
38
|
+
when "cleanup"
|
|
39
|
+
run_cleanup(args)
|
|
40
|
+
when "-h", "--help", nil
|
|
41
|
+
display_usage
|
|
42
|
+
else
|
|
43
|
+
display_message("Unknown subcommand: #{subcommand}", type: :error)
|
|
44
|
+
display_usage
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def run_migrate(args)
|
|
51
|
+
dry_run = false
|
|
52
|
+
backup = true
|
|
53
|
+
force = false
|
|
54
|
+
|
|
55
|
+
until args.empty?
|
|
56
|
+
token = args.shift
|
|
57
|
+
case token
|
|
58
|
+
when "--dry-run"
|
|
59
|
+
dry_run = true
|
|
60
|
+
when "--no-backup"
|
|
61
|
+
backup = false
|
|
62
|
+
when "--force"
|
|
63
|
+
force = true
|
|
64
|
+
when "-h", "--help"
|
|
65
|
+
display_migrate_usage
|
|
66
|
+
return
|
|
67
|
+
else
|
|
68
|
+
display_message("Unknown option: #{token}", type: :error)
|
|
69
|
+
display_migrate_usage
|
|
70
|
+
return
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
migrator = Database::StorageMigrator.new(
|
|
75
|
+
project_dir: @project_dir,
|
|
76
|
+
dry_run: dry_run
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Check if migration is needed
|
|
80
|
+
unless migrator.migration_needed?
|
|
81
|
+
display_message("No file-based storage found. Nothing to migrate.", type: :info)
|
|
82
|
+
return
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Check if already migrated
|
|
86
|
+
if migrator.already_migrated? && !force
|
|
87
|
+
display_message("Database already contains migrated data.", type: :warning)
|
|
88
|
+
display_message("Use --force to migrate anyway (may create duplicates).", type: :info)
|
|
89
|
+
return
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Confirm migration
|
|
93
|
+
if dry_run
|
|
94
|
+
display_message("DRY RUN: No changes will be made.", type: :highlight)
|
|
95
|
+
else
|
|
96
|
+
unless force
|
|
97
|
+
confirmed = @prompt.yes?("Migrate file-based storage to SQLite?")
|
|
98
|
+
return unless confirmed
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Run migration
|
|
103
|
+
display_message("Starting storage migration...", type: :info)
|
|
104
|
+
|
|
105
|
+
result = migrator.migrate!(backup: backup)
|
|
106
|
+
|
|
107
|
+
# Display results
|
|
108
|
+
display_migration_results(result)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def run_status
|
|
112
|
+
migrator = Database::StorageMigrator.new(project_dir: @project_dir)
|
|
113
|
+
|
|
114
|
+
display_message("\nStorage Migration Status", type: :info)
|
|
115
|
+
display_message("=" * 40, type: :muted)
|
|
116
|
+
|
|
117
|
+
if migrator.migration_needed?
|
|
118
|
+
display_message("File-based storage: Found", type: :warning)
|
|
119
|
+
display_message(" Migration recommended", type: :info)
|
|
120
|
+
else
|
|
121
|
+
display_message("File-based storage: Not found", type: :success)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
if migrator.already_migrated?
|
|
125
|
+
display_message("SQLite database: Contains data", type: :success)
|
|
126
|
+
else
|
|
127
|
+
display_message("SQLite database: Empty or not initialized", type: :info)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
db_file = ConfigPaths.database_file(@project_dir)
|
|
131
|
+
if File.exist?(db_file)
|
|
132
|
+
size = File.size(db_file)
|
|
133
|
+
display_message("Database file: #{db_file}", type: :info)
|
|
134
|
+
display_message("Database size: #{format_size(size)}", type: :info)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def run_cleanup(args)
|
|
139
|
+
keep_config = true
|
|
140
|
+
force = false
|
|
141
|
+
|
|
142
|
+
until args.empty?
|
|
143
|
+
token = args.shift
|
|
144
|
+
case token
|
|
145
|
+
when "--include-config"
|
|
146
|
+
keep_config = false
|
|
147
|
+
when "--force"
|
|
148
|
+
force = true
|
|
149
|
+
when "-h", "--help"
|
|
150
|
+
display_cleanup_usage
|
|
151
|
+
return
|
|
152
|
+
else
|
|
153
|
+
display_message("Unknown option: #{token}", type: :error)
|
|
154
|
+
display_cleanup_usage
|
|
155
|
+
return
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
migrator = Database::StorageMigrator.new(project_dir: @project_dir)
|
|
160
|
+
|
|
161
|
+
# Verify migration was completed
|
|
162
|
+
unless migrator.already_migrated?
|
|
163
|
+
display_message("Cannot cleanup: No migrated data found in database.", type: :error)
|
|
164
|
+
display_message("Run 'aidp storage migrate' first.", type: :info)
|
|
165
|
+
return
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
unless migrator.migration_needed?
|
|
169
|
+
display_message("No file-based storage to clean up.", type: :info)
|
|
170
|
+
return
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Confirm cleanup
|
|
174
|
+
unless force
|
|
175
|
+
display_message("This will permanently delete old file-based storage.", type: :warning)
|
|
176
|
+
confirmed = @prompt.yes?("Proceed with cleanup?")
|
|
177
|
+
return unless confirmed
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
display_message("Cleaning up old storage...", type: :info)
|
|
181
|
+
migrator.cleanup_old_storage!(keep_config: keep_config)
|
|
182
|
+
|
|
183
|
+
display_message("Cleanup complete.", type: :success)
|
|
184
|
+
display_message("Files removed: #{migrator.stats[:files_removed]}", type: :info)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def display_migration_results(result)
|
|
188
|
+
display_message("\nMigration Results", type: :info)
|
|
189
|
+
display_message("=" * 40, type: :muted)
|
|
190
|
+
|
|
191
|
+
case result[:status]
|
|
192
|
+
when :success
|
|
193
|
+
display_message("Status: Success", type: :success)
|
|
194
|
+
when :partial
|
|
195
|
+
display_message("Status: Partial (some errors occurred)", type: :warning)
|
|
196
|
+
when :skipped
|
|
197
|
+
display_message("Status: Skipped - #{result[:reason]}", type: :info)
|
|
198
|
+
return
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
if result[:dry_run]
|
|
202
|
+
display_message("(Dry run - no changes made)", type: :highlight)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
stats = result[:stats]
|
|
206
|
+
if stats && !stats.empty?
|
|
207
|
+
display_message("\nMigrated:", type: :info)
|
|
208
|
+
stats.each do |key, count|
|
|
209
|
+
next if count.zero?
|
|
210
|
+
label = key.to_s.tr("_", " ").capitalize
|
|
211
|
+
display_message(" #{label}: #{count}", type: :info)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
errors = result[:errors]
|
|
216
|
+
if errors && !errors.empty?
|
|
217
|
+
display_message("\nErrors:", type: :error)
|
|
218
|
+
errors.each do |error|
|
|
219
|
+
display_message(" #{error[:type]}: #{error[:error]}", type: :error)
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
unless result[:dry_run]
|
|
224
|
+
display_message("\nNext steps:", type: :info)
|
|
225
|
+
display_message(" 1. Verify data with 'aidp storage status'", type: :info)
|
|
226
|
+
display_message(" 2. Test AIDP functionality", type: :info)
|
|
227
|
+
display_message(" 3. Run 'aidp storage cleanup' to remove old files", type: :info)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def display_usage
|
|
232
|
+
display_message("\nUsage: aidp storage <subcommand> [options]", type: :info)
|
|
233
|
+
display_message("\nSubcommands:", type: :info)
|
|
234
|
+
display_message(" migrate Migrate file-based storage to SQLite", type: :info)
|
|
235
|
+
display_message(" status Show storage migration status", type: :info)
|
|
236
|
+
display_message(" cleanup Remove old file-based storage", type: :info)
|
|
237
|
+
display_message("\nOptions:", type: :info)
|
|
238
|
+
display_message(" -h, --help Show this help message", type: :info)
|
|
239
|
+
display_message("\nExamples:", type: :info)
|
|
240
|
+
display_message(" aidp storage migrate", type: :info)
|
|
241
|
+
display_message(" aidp storage migrate --dry-run", type: :info)
|
|
242
|
+
display_message(" aidp storage status", type: :info)
|
|
243
|
+
display_message(" aidp storage cleanup", type: :info)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def display_migrate_usage
|
|
247
|
+
display_message("\nUsage: aidp storage migrate [options]", type: :info)
|
|
248
|
+
display_message("\nOptions:", type: :info)
|
|
249
|
+
display_message(" --dry-run Show what would be migrated without making changes", type: :info)
|
|
250
|
+
display_message(" --no-backup Skip creating backup of .aidp directory", type: :info)
|
|
251
|
+
display_message(" --force Migrate even if database already has data", type: :info)
|
|
252
|
+
display_message(" -h, --help Show this help message", type: :info)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def display_cleanup_usage
|
|
256
|
+
display_message("\nUsage: aidp storage cleanup [options]", type: :info)
|
|
257
|
+
display_message("\nOptions:", type: :info)
|
|
258
|
+
display_message(" --include-config Also remove aidp.yml config file", type: :info)
|
|
259
|
+
display_message(" --force Skip confirmation prompt", type: :info)
|
|
260
|
+
display_message(" -h, --help Show this help message", type: :info)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def format_size(bytes)
|
|
264
|
+
units = %w[B KB MB GB]
|
|
265
|
+
unit = 0
|
|
266
|
+
size = bytes.to_f
|
|
267
|
+
|
|
268
|
+
while size >= 1024 && unit < units.length - 1
|
|
269
|
+
size /= 1024
|
|
270
|
+
unit += 1
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
"%.1f %s" % [size, units[unit]]
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|