aidp 0.13.0 → 0.14.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/README.md +7 -0
- data/lib/aidp/cli/first_run_wizard.rb +28 -303
- data/lib/aidp/cli/issue_importer.rb +359 -0
- data/lib/aidp/cli.rb +151 -3
- data/lib/aidp/daemon/process_manager.rb +146 -0
- data/lib/aidp/daemon/runner.rb +232 -0
- data/lib/aidp/execute/async_work_loop_runner.rb +216 -0
- data/lib/aidp/execute/future_work_backlog.rb +411 -0
- data/lib/aidp/execute/guard_policy.rb +246 -0
- data/lib/aidp/execute/instruction_queue.rb +131 -0
- data/lib/aidp/execute/interactive_repl.rb +335 -0
- data/lib/aidp/execute/repl_macros.rb +651 -0
- data/lib/aidp/execute/steps.rb +8 -0
- data/lib/aidp/execute/work_loop_runner.rb +322 -36
- data/lib/aidp/execute/work_loop_state.rb +162 -0
- data/lib/aidp/harness/config_schema.rb +88 -0
- data/lib/aidp/harness/configuration.rb +48 -1
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +2 -0
- data/lib/aidp/init/doc_generator.rb +256 -0
- data/lib/aidp/init/project_analyzer.rb +343 -0
- data/lib/aidp/init/runner.rb +83 -0
- data/lib/aidp/init.rb +5 -0
- data/lib/aidp/logger.rb +279 -0
- data/lib/aidp/setup/wizard.rb +777 -0
- data/lib/aidp/tooling_detector.rb +115 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +282 -0
- data/lib/aidp/watch/plan_generator.rb +166 -0
- data/lib/aidp/watch/plan_processor.rb +83 -0
- data/lib/aidp/watch/repository_client.rb +243 -0
- data/lib/aidp/watch/runner.rb +93 -0
- data/lib/aidp/watch/state_store.rb +105 -0
- data/lib/aidp/watch.rb +9 -0
- data/lib/aidp.rb +14 -0
- data/templates/implementation/simple_task.md +36 -0
- metadata +26 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 75047af72249152053d5dfe5b5e7800c0c8c844c7a513cf227601b8adb006af2
|
4
|
+
data.tar.gz: 574d521b5065f17afa618cfd6b672ed834badca459631011dddde7888796d7c6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 323d8e0cfe1f37934d703488c00cbb4d1769bc0dea9d6f2d826a1a995861e6a8c84d697963a5e9880e5555e1d3e3f32f551f5db8c7ca273de8d79514dbad0e70
|
7
|
+
data.tar.gz: 98c37900f0672cade4a7fa838ae94282355e8645fc0ea9a0e2a2e6b2c3d78d265f91e4f66c0f592cf1b8d3297984fdc6fdc26d8efd584df8a0b0c2b543c1f3b8
|
data/README.md
CHANGED
@@ -11,6 +11,13 @@ gem install aidp
|
|
11
11
|
# Navigate to your project
|
12
12
|
cd /your/project
|
13
13
|
|
14
|
+
# Launch the interactive configuration wizard
|
15
|
+
aidp config --interactive
|
16
|
+
|
17
|
+
# Analyze and bootstrap project docs
|
18
|
+
aidp init
|
19
|
+
# Creates LLM_STYLE_GUIDE.md, PROJECT_ANALYSIS.md, CODE_QUALITY_PLAN.md
|
20
|
+
|
14
21
|
# Start an interactive workflow
|
15
22
|
aidp execute
|
16
23
|
|
@@ -1,44 +1,36 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "
|
4
|
+
require "time"
|
5
5
|
require "tty-prompt"
|
6
|
-
require_relative "../harness/provider_factory"
|
7
|
-
require_relative "../config/paths"
|
8
6
|
|
9
7
|
module Aidp
|
10
8
|
class CLI
|
11
|
-
#
|
9
|
+
# Wrapper around Aidp::Setup::Wizard to preserve existing CLI entry points.
|
12
10
|
class FirstRunWizard
|
13
11
|
include Aidp::MessageDisplay
|
14
12
|
|
15
|
-
TEMPLATES_DIR = File.expand_path(File.join(__dir__, "..", "..", "..", "templates"))
|
16
|
-
|
17
13
|
def self.ensure_config(project_dir, non_interactive: false, prompt: TTY::Prompt.new)
|
18
14
|
return true if Aidp::Config.config_exists?(project_dir)
|
19
15
|
|
20
16
|
wizard = new(project_dir, prompt: prompt)
|
21
17
|
|
22
18
|
if non_interactive
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
19
|
+
wizard.create_minimal_config
|
20
|
+
wizard.send(:display_message, "Created minimal configuration (non-interactive default)", type: :success)
|
21
|
+
true
|
22
|
+
else
|
23
|
+
wizard.run
|
27
24
|
end
|
28
|
-
|
29
|
-
wizard.run
|
30
25
|
end
|
31
26
|
|
32
27
|
def self.setup_config(project_dir, non_interactive: false, prompt: TTY::Prompt.new)
|
33
|
-
wizard = new(project_dir, prompt: prompt)
|
34
|
-
|
35
28
|
if non_interactive
|
36
|
-
|
37
|
-
wizard.send(:display_message, "Configuration setup skipped in non-interactive environment", type: :info)
|
29
|
+
new(project_dir, prompt: prompt).send(:display_message, "Configuration setup skipped in non-interactive environment", type: :info)
|
38
30
|
return true
|
39
31
|
end
|
40
32
|
|
41
|
-
|
33
|
+
new(project_dir, prompt: prompt).run
|
42
34
|
end
|
43
35
|
|
44
36
|
def initialize(project_dir, prompt: TTY::Prompt.new)
|
@@ -47,300 +39,33 @@ module Aidp
|
|
47
39
|
end
|
48
40
|
|
49
41
|
def run
|
50
|
-
|
51
|
-
|
52
|
-
finish(run_custom)
|
53
|
-
end
|
54
|
-
|
55
|
-
def run_setup_config
|
56
|
-
@prompt.say("🔧 Configuration Setup", color: :blue)
|
57
|
-
@prompt.say("Setting up your configuration file with current values as defaults.")
|
58
|
-
@prompt.say("")
|
59
|
-
|
60
|
-
# Load existing config to use as defaults (if it exists)
|
61
|
-
existing_config = load_existing_config
|
62
|
-
|
63
|
-
if existing_config
|
64
|
-
# Run custom configuration with existing values as defaults
|
65
|
-
finish(run_custom_with_defaults(existing_config))
|
66
|
-
else
|
67
|
-
# No existing config, run the normal setup flow
|
68
|
-
@prompt.say("No existing configuration found. Running first-time setup...")
|
69
|
-
@prompt.say("")
|
70
|
-
run
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
private
|
75
|
-
|
76
|
-
def banner
|
77
|
-
display_message("\n🚀 First-time setup detected", type: :highlight)
|
78
|
-
display_message("No 'aidp.yml' configuration file found in #{relative(@project_dir)}.")
|
79
|
-
display_message("Creating a configuration so you can start using AI Dev Pipeline.")
|
80
|
-
display_message("")
|
81
|
-
end
|
82
|
-
|
83
|
-
def finish(path)
|
84
|
-
if path
|
85
|
-
display_message("\n✅ Configuration created at #{relative(path)}", type: :success)
|
86
|
-
display_message("You can edit this file anytime. Continuing startup...\n")
|
87
|
-
true
|
88
|
-
else
|
89
|
-
display_message("❌ Failed to create configuration file.", type: :error)
|
90
|
-
false
|
91
|
-
end
|
42
|
+
wizard = Aidp::Setup::Wizard.new(@project_dir, prompt: @prompt)
|
43
|
+
wizard.run
|
92
44
|
end
|
93
45
|
|
94
|
-
def
|
95
|
-
src = File.join(TEMPLATES_DIR, filename)
|
96
|
-
unless File.exist?(src)
|
97
|
-
display_message("Template not found: #{filename}", type: :error)
|
98
|
-
return nil
|
99
|
-
end
|
100
|
-
dest = Aidp::ConfigPaths.config_file(@project_dir)
|
46
|
+
def create_minimal_config
|
101
47
|
Aidp::ConfigPaths.ensure_config_dir(@project_dir)
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
def write_minimal_config(project_dir)
|
107
|
-
dest = Aidp::ConfigPaths.config_file(project_dir)
|
108
|
-
return dest if File.exist?(dest)
|
109
|
-
data = {
|
110
|
-
"harness" => {
|
111
|
-
"max_retries" => 2,
|
112
|
-
"default_provider" => "cursor",
|
113
|
-
"fallback_providers" => ["cursor"],
|
114
|
-
"no_api_keys_required" => false
|
115
|
-
},
|
48
|
+
minimal = {
|
49
|
+
"schema_version" => Aidp::Setup::Wizard::SCHEMA_VERSION,
|
50
|
+
"generated_by" => "aidp setup wizard minimal",
|
51
|
+
"generated_at" => Time.now.utc.iso8601,
|
116
52
|
"providers" => {
|
117
|
-
"
|
118
|
-
"
|
119
|
-
"
|
53
|
+
"llm" => {
|
54
|
+
"name" => "cursor",
|
55
|
+
"model" => "cursor-agent",
|
56
|
+
"temperature" => 0.2,
|
57
|
+
"max_tokens" => 1024
|
120
58
|
}
|
121
|
-
}
|
122
|
-
}
|
123
|
-
Aidp::ConfigPaths.ensure_config_dir(project_dir)
|
124
|
-
File.write(dest, YAML.dump(data))
|
125
|
-
dest
|
126
|
-
end
|
127
|
-
|
128
|
-
def write_example_config(project_dir)
|
129
|
-
Aidp::Config.create_example_config(project_dir)
|
130
|
-
Aidp::ConfigPaths.config_file(project_dir)
|
131
|
-
end
|
132
|
-
|
133
|
-
def run_custom
|
134
|
-
dest = Aidp::ConfigPaths.config_file(@project_dir)
|
135
|
-
return dest if File.exist?(dest)
|
136
|
-
|
137
|
-
@prompt.say("Interactive custom configuration: press Enter to accept defaults shown in [brackets].")
|
138
|
-
@prompt.say("")
|
139
|
-
|
140
|
-
# Use TTY::Prompt select for primary provider
|
141
|
-
# Find the formatted string that matches the default
|
142
|
-
default_option = available_providers.find { |option| option.start_with?("cursor -") } || available_providers.first
|
143
|
-
default_provider = @prompt.select("Default provider?", available_providers, default: default_option)
|
144
|
-
|
145
|
-
# Extract just the provider name from the formatted string
|
146
|
-
provider_name = default_provider.split(" - ").first
|
147
|
-
|
148
|
-
# Validate fallback providers
|
149
|
-
fallback_providers = select_fallback_providers(available_providers, provider_name)
|
150
|
-
|
151
|
-
restrict = @prompt.yes?("Only use providers that don't require API keys?", default: false)
|
152
|
-
|
153
|
-
# Process providers preserving order
|
154
|
-
providers = [provider_name] + fallback_providers
|
155
|
-
providers.uniq!
|
156
|
-
|
157
|
-
provider_section = {}
|
158
|
-
providers.each do |prov|
|
159
|
-
provider_section[prov] = {"type" => (prov == "cursor") ? "subscription" : "usage_based", "default_flags" => []}
|
160
|
-
end
|
161
|
-
|
162
|
-
data = {
|
163
|
-
"harness" => {
|
164
|
-
"max_retries" => 2,
|
165
|
-
"default_provider" => provider_name,
|
166
|
-
"fallback_providers" => fallback_providers,
|
167
|
-
"no_api_keys_required" => restrict
|
168
|
-
},
|
169
|
-
"providers" => provider_section
|
170
|
-
}
|
171
|
-
Aidp::ConfigPaths.ensure_config_dir(@project_dir)
|
172
|
-
File.write(dest, YAML.dump(data))
|
173
|
-
dest
|
174
|
-
end
|
175
|
-
|
176
|
-
def run_custom_with_defaults(existing_config)
|
177
|
-
dest = Aidp::ConfigPaths.config_file(@project_dir)
|
178
|
-
|
179
|
-
# Extract current values from existing config
|
180
|
-
harness_config = existing_config[:harness] || existing_config["harness"] || {}
|
181
|
-
providers_config = existing_config[:providers] || existing_config["providers"] || {}
|
182
|
-
|
183
|
-
current_default = harness_config[:default_provider] || harness_config["default_provider"] || "cursor"
|
184
|
-
current_fallbacks = harness_config[:fallback_providers] || harness_config["fallback_providers"] || [current_default]
|
185
|
-
current_restrict = harness_config[:no_api_keys_required] || harness_config["no_api_keys_required"] || false
|
186
|
-
|
187
|
-
# Use TTY::Prompt for interactive configuration
|
188
|
-
@prompt.say("Interactive configuration update: press Enter to keep current values shown in [brackets].")
|
189
|
-
@prompt.say("")
|
190
|
-
|
191
|
-
# Use TTY::Prompt select for primary provider
|
192
|
-
# Find the formatted string that matches the current default
|
193
|
-
default_option = available_providers.find { |option| option.start_with?("#{current_default} -") } || available_providers.first
|
194
|
-
default_provider = @prompt.select("Default provider?", available_providers, default: default_option)
|
195
|
-
|
196
|
-
# Extract just the provider name from the formatted string
|
197
|
-
provider_name = default_provider.split(" - ").first
|
198
|
-
|
199
|
-
# Validate fallback providers
|
200
|
-
fallback_providers = select_fallback_providers(available_providers, provider_name, preselected: current_fallbacks - [provider_name])
|
201
|
-
|
202
|
-
restrict_input = @prompt.yes?("Only use providers that don't require API keys?", default: current_restrict)
|
203
|
-
|
204
|
-
# Process providers preserving order
|
205
|
-
providers = [provider_name] + fallback_providers
|
206
|
-
providers.uniq!
|
207
|
-
|
208
|
-
# Build provider section
|
209
|
-
provider_section = {}
|
210
|
-
providers.each do |prov|
|
211
|
-
# Try to preserve existing provider config if it exists
|
212
|
-
existing_provider = providers_config[prov.to_sym] || providers_config[prov.to_s]
|
213
|
-
if existing_provider
|
214
|
-
# Convert existing provider config to string keys
|
215
|
-
converted_provider = {}
|
216
|
-
existing_provider.each { |k, v| converted_provider[k.to_s] = v }
|
217
|
-
# Ensure the type is correct (fix old "package" and "api" types)
|
218
|
-
if converted_provider["type"] == "package"
|
219
|
-
converted_provider["type"] = "subscription"
|
220
|
-
elsif converted_provider["type"] == "api"
|
221
|
-
converted_provider["type"] = "usage_based"
|
222
|
-
end
|
223
|
-
provider_section[prov] = converted_provider
|
224
|
-
else
|
225
|
-
provider_section[prov] = {"type" => (prov == "cursor") ? "subscription" : "usage_based", "default_flags" => []}
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
|
-
# Build the new config
|
230
|
-
data = {
|
231
|
-
"harness" => {
|
232
|
-
"max_retries" => harness_config[:max_retries] || harness_config["max_retries"] || 2,
|
233
|
-
"default_provider" => provider_name,
|
234
|
-
"fallback_providers" => fallback_providers,
|
235
|
-
"no_api_keys_required" => restrict_input
|
236
59
|
},
|
237
|
-
"
|
60
|
+
"work_loop" => {
|
61
|
+
"test" => {
|
62
|
+
"unit" => "bundle exec rspec",
|
63
|
+
"timeout_seconds" => 1800
|
64
|
+
}
|
65
|
+
}
|
238
66
|
}
|
239
67
|
|
240
|
-
Aidp::ConfigPaths.
|
241
|
-
File.write(dest, YAML.dump(data))
|
242
|
-
dest
|
243
|
-
end
|
244
|
-
|
245
|
-
def load_existing_config
|
246
|
-
config_file = Aidp::ConfigPaths.config_file(@project_dir)
|
247
|
-
return nil unless File.exist?(config_file)
|
248
|
-
|
249
|
-
begin
|
250
|
-
YAML.load_file(config_file) || {}
|
251
|
-
rescue => e
|
252
|
-
@prompt.say("❌ Failed to load existing configuration: #{e.message}", color: :red)
|
253
|
-
nil
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
|
-
def ask(prompt, default: nil)
|
258
|
-
if default
|
259
|
-
@prompt.ask("#{prompt}:", default: default)
|
260
|
-
else
|
261
|
-
@prompt.ask("#{prompt}:")
|
262
|
-
end
|
263
|
-
end
|
264
|
-
|
265
|
-
def relative(path)
|
266
|
-
pn = Pathname.new(path)
|
267
|
-
wd = Pathname.new(@project_dir)
|
268
|
-
rel = pn.relative_path_from(wd).to_s
|
269
|
-
rel.start_with?("..") ? path : rel
|
270
|
-
rescue
|
271
|
-
path
|
272
|
-
end
|
273
|
-
|
274
|
-
# Get available providers for validation
|
275
|
-
def available_providers
|
276
|
-
# Get all supported providers from the factory (single source of truth)
|
277
|
-
all_providers = Aidp::Harness::ProviderFactory::PROVIDER_CLASSES.keys
|
278
|
-
|
279
|
-
# Filter out providers we don't want to show in the wizard
|
280
|
-
# - "anthropic" is an internal name, we show "claude" instead
|
281
|
-
# - "macos" is disabled (as per issue #73)
|
282
|
-
excluded = ["anthropic", "macos"]
|
283
|
-
available = all_providers - excluded
|
284
|
-
|
285
|
-
# Get display names from the providers themselves
|
286
|
-
available.map do |provider_name|
|
287
|
-
provider_class = Aidp::Harness::ProviderFactory::PROVIDER_CLASSES[provider_name]
|
288
|
-
if provider_class
|
289
|
-
# Instantiate to get display name
|
290
|
-
instance = provider_class.new
|
291
|
-
display_name = instance.display_name
|
292
|
-
"#{provider_name} - #{display_name}"
|
293
|
-
else
|
294
|
-
provider_name
|
295
|
-
end
|
296
|
-
end
|
297
|
-
end
|
298
|
-
|
299
|
-
# Validate provider list input
|
300
|
-
def validate_provider_list(input, available_providers)
|
301
|
-
return true if input.nil? || input.empty?
|
302
|
-
|
303
|
-
# Extract provider names from the input
|
304
|
-
providers = input.split(/\s*,\s*/).map(&:strip).reject(&:empty?)
|
305
|
-
|
306
|
-
# Check if all providers are valid
|
307
|
-
valid_providers = available_providers.map { |p| p.split(" - ").first }
|
308
|
-
providers.all? { |provider| valid_providers.include?(provider) }
|
309
|
-
end
|
310
|
-
|
311
|
-
# Interactive ordered multi-select for fallback providers
|
312
|
-
def select_fallback_providers(available_with_labels, default_provider, preselected: [])
|
313
|
-
# Extract provider names and exclude the already chosen default
|
314
|
-
options = available_with_labels.map { |o| o.split(" - ").first }
|
315
|
-
candidates = options.reject { |p| p == default_provider }
|
316
|
-
|
317
|
-
return [] if candidates.empty?
|
318
|
-
|
319
|
-
selected = preselected.select { |p| candidates.include?(p) }
|
320
|
-
|
321
|
-
loop do
|
322
|
-
display_message("\nSelect fallback providers in order of preference (first = highest priority).", type: :info)
|
323
|
-
display_message("Current order: #{selected.empty? ? "(none)" : selected.join(" > ")}", type: :muted)
|
324
|
-
choice = @prompt.select("Add provider, or choose an action:", cycle: true) do |menu|
|
325
|
-
(candidates - selected).each { |prov| menu.choice("Add #{prov}", prov) }
|
326
|
-
menu.choice("Done", :done)
|
327
|
-
menu.choice("Clear", :clear) unless selected.empty?
|
328
|
-
menu.choice("Remove last (#{selected.last})", :remove) unless selected.empty?
|
329
|
-
end
|
330
|
-
|
331
|
-
case choice
|
332
|
-
when :done
|
333
|
-
break
|
334
|
-
when :clear
|
335
|
-
selected.clear
|
336
|
-
when :remove
|
337
|
-
selected.pop
|
338
|
-
else
|
339
|
-
selected << choice unless selected.include?(choice)
|
340
|
-
end
|
341
|
-
end
|
342
|
-
|
343
|
-
selected
|
68
|
+
File.write(Aidp::ConfigPaths.config_file(@project_dir), minimal.to_yaml)
|
344
69
|
end
|
345
70
|
end
|
346
71
|
end
|