aidp 0.11.0 → 0.12.1
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/first_run_wizard.rb +11 -7
- data/lib/aidp/cli/mcp_dashboard.rb +205 -0
- data/lib/aidp/cli.rb +206 -6
- data/lib/aidp/config/paths.rb +131 -0
- data/lib/aidp/config.rb +18 -4
- data/lib/aidp/harness/config_validator.rb +4 -3
- data/lib/aidp/harness/configuration.rb +2 -1
- data/lib/aidp/harness/provider_info.rb +366 -0
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +22 -1
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/workflows/guided_agent.rb +400 -0
- metadata +5 -1
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
# Centralized path management for all AIDP internal files
|
|
7
|
+
# Ensures consistent file locations and prevents path-related bugs
|
|
8
|
+
module ConfigPaths
|
|
9
|
+
# Get the main AIDP directory for a project
|
|
10
|
+
def self.aidp_dir(project_dir = Dir.pwd)
|
|
11
|
+
File.join(project_dir, ".aidp")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Get the main configuration file path
|
|
15
|
+
def self.config_file(project_dir = Dir.pwd)
|
|
16
|
+
File.join(aidp_dir(project_dir), "aidp.yml")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Get the configuration directory path
|
|
20
|
+
def self.config_dir(project_dir = Dir.pwd)
|
|
21
|
+
aidp_dir(project_dir)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Get the progress directory path
|
|
25
|
+
def self.progress_dir(project_dir = Dir.pwd)
|
|
26
|
+
File.join(aidp_dir(project_dir), "progress")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Get the execute progress file path
|
|
30
|
+
def self.execute_progress_file(project_dir = Dir.pwd)
|
|
31
|
+
File.join(progress_dir(project_dir), "execute.yml")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Get the analyze progress file path
|
|
35
|
+
def self.analyze_progress_file(project_dir = Dir.pwd)
|
|
36
|
+
File.join(progress_dir(project_dir), "analyze.yml")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Get the harness state directory path
|
|
40
|
+
def self.harness_state_dir(project_dir = Dir.pwd)
|
|
41
|
+
File.join(aidp_dir(project_dir), "harness")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Get the harness state file path for a specific mode
|
|
45
|
+
def self.harness_state_file(mode, project_dir = Dir.pwd)
|
|
46
|
+
File.join(harness_state_dir(project_dir), "#{mode}_state.json")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Get the providers directory path
|
|
50
|
+
def self.providers_dir(project_dir = Dir.pwd)
|
|
51
|
+
File.join(aidp_dir(project_dir), "providers")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Get the provider info file path
|
|
55
|
+
def self.provider_info_file(provider_name, project_dir = Dir.pwd)
|
|
56
|
+
File.join(providers_dir(project_dir), "#{provider_name}_info.yml")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Get the jobs directory path
|
|
60
|
+
def self.jobs_dir(project_dir = Dir.pwd)
|
|
61
|
+
File.join(aidp_dir(project_dir), "jobs")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Get the checkpoint file path
|
|
65
|
+
def self.checkpoint_file(project_dir = Dir.pwd)
|
|
66
|
+
File.join(aidp_dir(project_dir), "checkpoint.yml")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Get the checkpoint history file path
|
|
70
|
+
def self.checkpoint_history_file(project_dir = Dir.pwd)
|
|
71
|
+
File.join(aidp_dir(project_dir), "checkpoint_history.jsonl")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Get the JSON storage directory path
|
|
75
|
+
def self.json_storage_dir(project_dir = Dir.pwd)
|
|
76
|
+
File.join(aidp_dir(project_dir), "json")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Check if the main configuration file exists
|
|
80
|
+
def self.config_exists?(project_dir = Dir.pwd)
|
|
81
|
+
File.exist?(config_file(project_dir))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Ensure the main AIDP directory exists
|
|
85
|
+
def self.ensure_aidp_dir(project_dir = Dir.pwd)
|
|
86
|
+
dir = aidp_dir(project_dir)
|
|
87
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
88
|
+
dir
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Ensure the configuration directory exists
|
|
92
|
+
def self.ensure_config_dir(project_dir = Dir.pwd)
|
|
93
|
+
ensure_aidp_dir(project_dir)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Ensure the progress directory exists
|
|
97
|
+
def self.ensure_progress_dir(project_dir = Dir.pwd)
|
|
98
|
+
dir = progress_dir(project_dir)
|
|
99
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
100
|
+
dir
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Ensure the harness state directory exists
|
|
104
|
+
def self.ensure_harness_state_dir(project_dir = Dir.pwd)
|
|
105
|
+
dir = harness_state_dir(project_dir)
|
|
106
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
107
|
+
dir
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Ensure the providers directory exists
|
|
111
|
+
def self.ensure_providers_dir(project_dir = Dir.pwd)
|
|
112
|
+
dir = providers_dir(project_dir)
|
|
113
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
114
|
+
dir
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Ensure the jobs directory exists
|
|
118
|
+
def self.ensure_jobs_dir(project_dir = Dir.pwd)
|
|
119
|
+
dir = jobs_dir(project_dir)
|
|
120
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
121
|
+
dir
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Ensure the JSON storage directory exists
|
|
125
|
+
def self.ensure_json_storage_dir(project_dir = Dir.pwd)
|
|
126
|
+
dir = json_storage_dir(project_dir)
|
|
127
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
128
|
+
dir
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
data/lib/aidp/config.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "yaml"
|
|
4
|
+
require_relative "config/paths"
|
|
4
5
|
|
|
5
6
|
module Aidp
|
|
6
7
|
# Configuration management for both execute and analyze modes
|
|
@@ -165,7 +166,7 @@ module Aidp
|
|
|
165
166
|
}.freeze
|
|
166
167
|
|
|
167
168
|
def self.load(project_dir = Dir.pwd)
|
|
168
|
-
config_file =
|
|
169
|
+
config_file = ConfigPaths.config_file(project_dir)
|
|
169
170
|
|
|
170
171
|
if File.exist?(config_file)
|
|
171
172
|
load_yaml_config(config_file)
|
|
@@ -244,15 +245,15 @@ module Aidp
|
|
|
244
245
|
|
|
245
246
|
# Check if configuration file exists
|
|
246
247
|
def self.config_exists?(project_dir = Dir.pwd)
|
|
247
|
-
|
|
248
|
+
ConfigPaths.config_exists?(project_dir)
|
|
248
249
|
end
|
|
249
250
|
|
|
250
251
|
# Create example configuration file
|
|
251
252
|
def self.create_example_config(project_dir = Dir.pwd)
|
|
252
|
-
config_path =
|
|
253
|
+
config_path = ConfigPaths.config_file(project_dir)
|
|
253
254
|
return false if File.exist?(config_path)
|
|
254
255
|
|
|
255
|
-
|
|
256
|
+
ConfigPaths.ensure_config_dir(project_dir)
|
|
256
257
|
|
|
257
258
|
example_config = {
|
|
258
259
|
harness: {
|
|
@@ -283,6 +284,19 @@ module Aidp
|
|
|
283
284
|
true
|
|
284
285
|
end
|
|
285
286
|
|
|
287
|
+
# Expose path methods for convenience
|
|
288
|
+
def self.config_file(project_dir = Dir.pwd)
|
|
289
|
+
ConfigPaths.config_file(project_dir)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def self.config_dir(project_dir = Dir.pwd)
|
|
293
|
+
ConfigPaths.config_dir(project_dir)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def self.aidp_dir(project_dir = Dir.pwd)
|
|
297
|
+
ConfigPaths.aidp_dir(project_dir)
|
|
298
|
+
end
|
|
299
|
+
|
|
286
300
|
private_class_method def self.load_yaml_config(config_file)
|
|
287
301
|
YAML.load_file(config_file) || {}
|
|
288
302
|
rescue => e
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "yaml"
|
|
4
4
|
require_relative "config_schema"
|
|
5
|
+
require_relative "../config/paths"
|
|
5
6
|
|
|
6
7
|
module Aidp
|
|
7
8
|
module Harness
|
|
@@ -76,9 +77,9 @@ module Aidp
|
|
|
76
77
|
return false if config_exists?
|
|
77
78
|
|
|
78
79
|
example_config = ConfigSchema.generate_example
|
|
79
|
-
config_path =
|
|
80
|
+
config_path = Aidp::ConfigPaths.config_file(@project_dir)
|
|
80
81
|
|
|
81
|
-
|
|
82
|
+
Aidp::ConfigPaths.ensure_config_dir(@project_dir)
|
|
82
83
|
File.write(config_path, YAML.dump(example_config))
|
|
83
84
|
true
|
|
84
85
|
end
|
|
@@ -244,7 +245,7 @@ module Aidp
|
|
|
244
245
|
private
|
|
245
246
|
|
|
246
247
|
def find_config_file
|
|
247
|
-
config_file =
|
|
248
|
+
config_file = Aidp::ConfigPaths.config_file(@project_dir)
|
|
248
249
|
|
|
249
250
|
if File.exist?(config_file)
|
|
250
251
|
config_file
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../config"
|
|
4
|
+
require_relative "../config/paths"
|
|
4
5
|
|
|
5
6
|
module Aidp
|
|
6
7
|
module Harness
|
|
@@ -211,7 +212,7 @@ module Aidp
|
|
|
211
212
|
|
|
212
213
|
# Get configuration path
|
|
213
214
|
def config_path
|
|
214
|
-
|
|
215
|
+
Aidp::ConfigPaths.config_file(@project_dir)
|
|
215
216
|
end
|
|
216
217
|
|
|
217
218
|
# Get logging configuration
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "yaml"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
|
|
7
|
+
module Aidp
|
|
8
|
+
module Harness
|
|
9
|
+
# Stores detailed information about AI providers gathered from their CLI tools
|
|
10
|
+
class ProviderInfo
|
|
11
|
+
attr_reader :provider_name, :info_file_path
|
|
12
|
+
|
|
13
|
+
def initialize(provider_name, root_dir = nil)
|
|
14
|
+
@provider_name = provider_name
|
|
15
|
+
@root_dir = root_dir || Dir.pwd
|
|
16
|
+
@info_file_path = File.join(@root_dir, ".aidp", "providers", "#{provider_name}_info.yml")
|
|
17
|
+
ensure_directory_exists
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Gather information about the provider by introspecting its CLI
|
|
21
|
+
def gather_info
|
|
22
|
+
info = {
|
|
23
|
+
provider: @provider_name,
|
|
24
|
+
last_checked: Time.now.iso8601,
|
|
25
|
+
cli_available: false,
|
|
26
|
+
help_output: nil,
|
|
27
|
+
capabilities: {},
|
|
28
|
+
permission_modes: [],
|
|
29
|
+
mcp_support: false,
|
|
30
|
+
mcp_servers: [],
|
|
31
|
+
auth_method: nil,
|
|
32
|
+
flags: {}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Try to get help output from the provider CLI
|
|
36
|
+
help_output = fetch_help_output
|
|
37
|
+
if help_output
|
|
38
|
+
info[:cli_available] = true
|
|
39
|
+
info[:help_output] = help_output
|
|
40
|
+
info.merge!(parse_help_output(help_output))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Try to get MCP server list if supported
|
|
44
|
+
if info[:mcp_support]
|
|
45
|
+
mcp_servers = fetch_mcp_servers
|
|
46
|
+
info[:mcp_servers] = mcp_servers if mcp_servers
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
save_info(info)
|
|
50
|
+
info
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Load stored provider info
|
|
54
|
+
def load_info
|
|
55
|
+
return nil unless File.exist?(@info_file_path)
|
|
56
|
+
|
|
57
|
+
YAML.safe_load_file(@info_file_path, permitted_classes: [Time, Symbol])
|
|
58
|
+
rescue => e
|
|
59
|
+
warn "Failed to load provider info for #{@provider_name}: #{e.message}"
|
|
60
|
+
nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Get provider info, refreshing if needed
|
|
64
|
+
def get_info(force_refresh: false, max_age: 86400)
|
|
65
|
+
existing_info = load_info
|
|
66
|
+
|
|
67
|
+
# Refresh if forced, missing, or stale
|
|
68
|
+
if force_refresh || existing_info.nil? || info_stale?(existing_info, max_age)
|
|
69
|
+
gather_info
|
|
70
|
+
else
|
|
71
|
+
existing_info
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Check if provider supports MCP servers
|
|
76
|
+
def supports_mcp?
|
|
77
|
+
info = load_info
|
|
78
|
+
return false unless info
|
|
79
|
+
|
|
80
|
+
info[:mcp_support] == true
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Get permission modes available
|
|
84
|
+
def permission_modes
|
|
85
|
+
info = load_info
|
|
86
|
+
return [] unless info
|
|
87
|
+
|
|
88
|
+
info[:permission_modes] || []
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Get authentication method
|
|
92
|
+
def auth_method
|
|
93
|
+
info = load_info
|
|
94
|
+
return nil unless info
|
|
95
|
+
|
|
96
|
+
info[:auth_method]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Get available flags/options
|
|
100
|
+
def available_flags
|
|
101
|
+
info = load_info
|
|
102
|
+
return {} unless info
|
|
103
|
+
|
|
104
|
+
info[:flags] || {}
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Get configured MCP servers
|
|
108
|
+
def mcp_servers
|
|
109
|
+
info = load_info
|
|
110
|
+
return [] unless info
|
|
111
|
+
|
|
112
|
+
info[:mcp_servers] || []
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Check if provider has MCP servers configured
|
|
116
|
+
def has_mcp_servers?
|
|
117
|
+
mcp_servers.any?
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
def ensure_directory_exists
|
|
123
|
+
dir = File.dirname(@info_file_path)
|
|
124
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def save_info(info)
|
|
128
|
+
File.write(@info_file_path, YAML.dump(info))
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def info_stale?(info, max_age)
|
|
132
|
+
return true unless info[:last_checked]
|
|
133
|
+
|
|
134
|
+
last_checked = Time.parse(info[:last_checked].to_s)
|
|
135
|
+
(Time.now - last_checked) > max_age
|
|
136
|
+
rescue
|
|
137
|
+
true
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def fetch_help_output
|
|
141
|
+
execute_provider_command("--help")
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def fetch_mcp_servers
|
|
145
|
+
binary = get_binary_name
|
|
146
|
+
return nil unless binary
|
|
147
|
+
|
|
148
|
+
# Try different MCP list commands based on provider
|
|
149
|
+
mcp_output = case @provider_name
|
|
150
|
+
when "claude", "anthropic"
|
|
151
|
+
execute_provider_command("mcp", "list")
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
return nil unless mcp_output
|
|
155
|
+
|
|
156
|
+
parse_mcp_servers(mcp_output)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def execute_provider_command(*args)
|
|
160
|
+
binary = get_binary_name
|
|
161
|
+
return nil unless binary
|
|
162
|
+
|
|
163
|
+
# Try to find the binary
|
|
164
|
+
path = begin
|
|
165
|
+
Aidp::Util.which(binary)
|
|
166
|
+
rescue
|
|
167
|
+
nil
|
|
168
|
+
end
|
|
169
|
+
return nil unless path
|
|
170
|
+
|
|
171
|
+
# Execute command with timeout
|
|
172
|
+
begin
|
|
173
|
+
r, w = IO.pipe
|
|
174
|
+
pid = Process.spawn(binary, *args, out: w, err: w)
|
|
175
|
+
w.close
|
|
176
|
+
|
|
177
|
+
# Wait with timeout
|
|
178
|
+
deadline = Time.now + 5
|
|
179
|
+
status = nil
|
|
180
|
+
while Time.now < deadline
|
|
181
|
+
pid_done, status = Process.waitpid2(pid, Process::WNOHANG)
|
|
182
|
+
break if pid_done
|
|
183
|
+
sleep 0.05
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Kill if timed out
|
|
187
|
+
unless status
|
|
188
|
+
begin
|
|
189
|
+
Process.kill("TERM", pid)
|
|
190
|
+
sleep 0.1
|
|
191
|
+
Process.kill("KILL", pid)
|
|
192
|
+
rescue
|
|
193
|
+
nil
|
|
194
|
+
end
|
|
195
|
+
return nil
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
output = r.read
|
|
199
|
+
r.close
|
|
200
|
+
output
|
|
201
|
+
rescue
|
|
202
|
+
nil
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def parse_mcp_servers(output)
|
|
207
|
+
servers = []
|
|
208
|
+
return servers unless output
|
|
209
|
+
|
|
210
|
+
# Parse MCP server list output
|
|
211
|
+
# Claude format (as of 2025):
|
|
212
|
+
# dash-api: uvx --from git+https://... - ✓ Connected
|
|
213
|
+
# or
|
|
214
|
+
# server-name: command - ✗ Error message
|
|
215
|
+
#
|
|
216
|
+
# Legacy format:
|
|
217
|
+
# Name Status Description
|
|
218
|
+
# filesystem enabled File system access
|
|
219
|
+
|
|
220
|
+
lines = output.lines
|
|
221
|
+
|
|
222
|
+
# Skip header lines
|
|
223
|
+
lines.reject! { |line| /checking mcp server health/i.match?(line) }
|
|
224
|
+
|
|
225
|
+
lines.each do |line|
|
|
226
|
+
line = line.strip
|
|
227
|
+
next if line.empty?
|
|
228
|
+
|
|
229
|
+
# Try to parse new Claude format: "name: command - ✓ Connected"
|
|
230
|
+
if line =~ /^([^:]+):\s*(.+?)\s*-\s*(✓|✗)\s*(.+)$/
|
|
231
|
+
name = Regexp.last_match(1).strip
|
|
232
|
+
command = Regexp.last_match(2).strip
|
|
233
|
+
status_symbol = Regexp.last_match(3)
|
|
234
|
+
status_text = Regexp.last_match(4).strip
|
|
235
|
+
|
|
236
|
+
servers << {
|
|
237
|
+
name: name,
|
|
238
|
+
status: (status_symbol == "✓") ? "connected" : "error",
|
|
239
|
+
description: command,
|
|
240
|
+
enabled: status_symbol == "✓",
|
|
241
|
+
error: (status_symbol == "✗") ? status_text : nil
|
|
242
|
+
}
|
|
243
|
+
next
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Try to parse legacy table format
|
|
247
|
+
# Skip header line
|
|
248
|
+
next if /Name.*Status/i.match?(line)
|
|
249
|
+
next if /^[-=]+$/.match?(line) # Skip separator lines
|
|
250
|
+
|
|
251
|
+
# Parse table format: columns separated by multiple spaces
|
|
252
|
+
parts = line.split(/\s{2,}/)
|
|
253
|
+
next if parts.size < 2
|
|
254
|
+
|
|
255
|
+
name = parts[0]&.strip
|
|
256
|
+
status = parts[1]&.strip
|
|
257
|
+
description = parts[2..]&.join(" ")&.strip
|
|
258
|
+
|
|
259
|
+
next unless name && !name.empty?
|
|
260
|
+
|
|
261
|
+
servers << {
|
|
262
|
+
name: name,
|
|
263
|
+
status: status || "unknown",
|
|
264
|
+
description: description,
|
|
265
|
+
enabled: status&.downcase == "enabled" || status&.downcase == "connected"
|
|
266
|
+
}
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
servers
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def get_binary_name
|
|
273
|
+
case @provider_name
|
|
274
|
+
when "claude", "anthropic"
|
|
275
|
+
"claude"
|
|
276
|
+
when "cursor"
|
|
277
|
+
"cursor"
|
|
278
|
+
when "gemini"
|
|
279
|
+
"gemini"
|
|
280
|
+
when "codex"
|
|
281
|
+
"codex"
|
|
282
|
+
when "github_copilot"
|
|
283
|
+
"gh"
|
|
284
|
+
when "opencode"
|
|
285
|
+
"opencode"
|
|
286
|
+
else
|
|
287
|
+
@provider_name
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def parse_help_output(help_text)
|
|
292
|
+
parsed = {
|
|
293
|
+
capabilities: {},
|
|
294
|
+
permission_modes: [],
|
|
295
|
+
mcp_support: false,
|
|
296
|
+
auth_method: nil,
|
|
297
|
+
flags: {}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
# Check for MCP support
|
|
301
|
+
parsed[:mcp_support] = !!(help_text =~ /mcp|MCP|Model Context Protocol/i)
|
|
302
|
+
|
|
303
|
+
# Extract permission modes
|
|
304
|
+
if help_text =~ /--permission-mode\s+<mode>\s+.*?\(choices:\s*([^)]+)\)/m
|
|
305
|
+
modes = Regexp.last_match(1).split(",").map(&:strip).map { |m| m.gsub(/["']/, "") }
|
|
306
|
+
parsed[:permission_modes] = modes
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Check for dangerous skip permissions
|
|
310
|
+
parsed[:capabilities][:bypass_permissions] = !!(help_text =~ /--dangerously-skip-permissions/)
|
|
311
|
+
|
|
312
|
+
# Check for API key / subscription patterns
|
|
313
|
+
if /--api-key|API_KEY|setup-token|subscription/i.match?(help_text)
|
|
314
|
+
parsed[:auth_method] = if /setup-token|subscription/i.match?(help_text)
|
|
315
|
+
"subscription"
|
|
316
|
+
else
|
|
317
|
+
"api_key"
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Extract model configuration
|
|
322
|
+
parsed[:capabilities][:model_selection] = !!(help_text =~ /--model\s+<model>/)
|
|
323
|
+
|
|
324
|
+
# Extract MCP configuration
|
|
325
|
+
parsed[:capabilities][:mcp_config] = !!(help_text =~ /--mcp-config/)
|
|
326
|
+
|
|
327
|
+
# Extract allowed/disallowed tools
|
|
328
|
+
parsed[:capabilities][:tool_restrictions] = !!(help_text =~ /--allowed-tools|--disallowed-tools/)
|
|
329
|
+
|
|
330
|
+
# Extract session management
|
|
331
|
+
parsed[:capabilities][:session_management] = !!(help_text =~ /--continue|--resume|--fork-session/)
|
|
332
|
+
|
|
333
|
+
# Extract output formats
|
|
334
|
+
if help_text =~ /--output-format\s+.*?\(choices:\s*([^)]+)\)/m
|
|
335
|
+
formats = Regexp.last_match(1).split(",").map(&:strip).map { |f| f.gsub(/["']/, "") }
|
|
336
|
+
parsed[:capabilities][:output_formats] = formats
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
# Extract notable flags
|
|
340
|
+
extract_flags(help_text, parsed[:flags])
|
|
341
|
+
|
|
342
|
+
parsed
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def extract_flags(help_text, flags_hash)
|
|
346
|
+
# Extract all flags with their descriptions
|
|
347
|
+
help_text.scan(/^\s+(--[\w-]+(?:\s+<\w+>)?)\s+(.+?)(?=^\s+(?:--|\w|$))/m).each do |flag, desc|
|
|
348
|
+
flag_name = flag.split.first.gsub(/^--/, "")
|
|
349
|
+
flags_hash[flag_name] = {
|
|
350
|
+
flag: flag.strip,
|
|
351
|
+
description: desc.strip.gsub(/\s+/, " ")
|
|
352
|
+
}
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# Also capture short flags
|
|
356
|
+
help_text.scan(/^\s+(-\w),\s+(--[\w-]+(?:\s+<\w+>)?)\s+(.+?)(?=^\s+(?:--|-\w|$))/m).each do |short, long, desc|
|
|
357
|
+
flag_name = long.split.first.gsub(/^--/, "")
|
|
358
|
+
flags_hash[flag_name] ||= {}
|
|
359
|
+
flags_hash[flag_name][:short] = short
|
|
360
|
+
flags_hash[flag_name][:flag] = long.strip
|
|
361
|
+
flags_hash[flag_name][:description] = desc.strip.gsub(/\s+/, " ")
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "enhanced_tui"
|
|
4
4
|
require_relative "../../workflows/selector"
|
|
5
|
+
require_relative "../../workflows/guided_agent"
|
|
5
6
|
|
|
6
7
|
module Aidp
|
|
7
8
|
module Harness
|
|
@@ -10,10 +11,11 @@ module Aidp
|
|
|
10
11
|
class EnhancedWorkflowSelector
|
|
11
12
|
class WorkflowError < StandardError; end
|
|
12
13
|
|
|
13
|
-
def initialize(tui = nil)
|
|
14
|
+
def initialize(tui = nil, project_dir: Dir.pwd)
|
|
14
15
|
@tui = tui || EnhancedTUI.new
|
|
15
16
|
@user_input = {}
|
|
16
17
|
@workflow_selector = Aidp::Workflows::Selector.new
|
|
18
|
+
@project_dir = project_dir
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def select_workflow(harness_mode: false, mode: :analyze)
|
|
@@ -28,6 +30,8 @@ module Aidp
|
|
|
28
30
|
|
|
29
31
|
def select_workflow_interactive(mode)
|
|
30
32
|
case mode
|
|
33
|
+
when :guided
|
|
34
|
+
select_guided_workflow
|
|
31
35
|
when :analyze
|
|
32
36
|
select_analyze_workflow_interactive
|
|
33
37
|
when :execute
|
|
@@ -243,6 +247,23 @@ module Aidp
|
|
|
243
247
|
"16_IMPLEMENTATION"
|
|
244
248
|
]
|
|
245
249
|
end
|
|
250
|
+
|
|
251
|
+
def select_guided_workflow
|
|
252
|
+
# Use the guided agent to help user select workflow
|
|
253
|
+
guided_agent = Aidp::Workflows::GuidedAgent.new(@project_dir, prompt: @tui.instance_variable_get(:@prompt))
|
|
254
|
+
result = guided_agent.select_workflow
|
|
255
|
+
|
|
256
|
+
# Store user input for later use
|
|
257
|
+
@user_input = result[:user_input]
|
|
258
|
+
|
|
259
|
+
# Return in the expected format
|
|
260
|
+
{
|
|
261
|
+
workflow_type: result[:workflow_type],
|
|
262
|
+
steps: result[:steps],
|
|
263
|
+
user_input: @user_input,
|
|
264
|
+
workflow: result[:workflow]
|
|
265
|
+
}
|
|
266
|
+
end
|
|
246
267
|
end
|
|
247
268
|
end
|
|
248
269
|
end
|
data/lib/aidp/version.rb
CHANGED